local UI = game.UI
local classlib = require("core.class")
local clone = require("core.clone")
local runlistlib = require("core.runlist")
local stack = require("core.stack")
local tablex = require("core.tablex")
local thunk = require("core.thunk")
local timerlib = require("core.timer")
local hooks = require("ui.hooks")
local util = require("ui.util")
local coreUtil = require("core.util")
local runList = runlistlib.RunList.New()
local latestGotoContext = 0
local fsms = {}
_G.uiFsms = fsms
local fsmList = {}
local GetFSMByName = function(name)
  return fsms[name]
end
local needToDoLateInit = true
local DoLateInitIfNeeded = function()
  if needToDoLateInit then
    for _, fsm in ipairs(fsmList) do
      engine.ProfileCall("fsm:_lateInit", fsm._lateInit, fsm)
    end
    needToDoLateInit = false
    collectgarbage()
  end
end
local EventProxySet = classlib.Class("EventProxySet")
function EventProxySet:init()
  self._priority2proxies = {}
  self._id2priority = {}
  self._proxy2id = {}
  self._idGen = coreUtil.CreateIdGen()
  self._sortedPriorities = {}
  self._sortedPrioritiesDirty = false
  self._iterating = 0
end
function EventProxySet:AddProxy(proxy, priority)
  assert(0 == self._iterating)
  local id = self._idGen:AllocId()
  local proxies = self._priority2proxies[priority]
  if nil == proxies then
    self._sortedPrioritiesDirty = true
    proxies = {}
    self._priority2proxies[priority] = proxies
  end
  proxies[1 + #proxies] = proxy
  self._proxy2id[proxy] = id
  self._id2priority[id] = priority
end
function EventProxySet:RemoveProxy(proxy)
  assert(0 == self._iterating)
  local id = self._proxy2id[proxy]
  local priority = self._id2priority[id]
  self._idGen:DeallocId(id)
  local proxies = self._priority2proxies[priority]
  local index
  for i, p in ipairs(proxies) do
    if proxy == p then
      index = i
      break
    end
  end
  table.remove(proxies, index)
end
function EventProxySet:GetIter()
  if self._sortedPrioritiesDirty then
    self._sortedPrioritiesDirty = false
    self._sortedPriorities = tablex.Keys(self._priority2proxies)
    table.sort(self._sortedPriorities)
  end
  local priorityIndex = 1
  local proxyIndex = 1
  self._iterating = self._iterating + 1
  return function()
    while priorityIndex <= #self._sortedPriorities do
      local priority = self._sortedPriorities[priorityIndex]
      local proxies = self._priority2proxies[priority]
      if proxyIndex <= #proxies then
        local proxy = proxies[proxyIndex]
        proxyIndex = proxyIndex + 1
        return {proxy, priority}
      else
        priorityIndex = priorityIndex + 1
        proxyIndex = 1
      end
    end
    self._iterating = self._iterating - 1
    return nil
  end
end
local UIState = classlib.Class("UIState")
local DeferredEvents = {}
local padCleared = false
local ResetPadClear = function()
  padCleared = false
end
local GetPadCleared = function()
  return padCleared
end
local HandleUIPadClear = function()
  padCleared = true
end
thunk.Install("HandleUIPadClear", HandleUIPadClear)
function UIState:init(name, parentOrHierarchy)
  assert(name, "UIState:init: name cannot be nil")
  self.name = name
  if parentOrHierarchy and classlib.isInstance(parentOrHierarchy) then
    self._parent = parentOrHierarchy
    self._hierarchy = nil
  else
    self._parent = nil
    self._hierarchy = parentOrHierarchy
  end
  self._children = {}
  self._uiElements = {}
  self._active = false
  self._activeChild = nil
  self._registeredUpdate = false
  self._initialized = false
  self._name2timer = {}
  self._eventProxies = nil
  self._debugPrintEnabled = true
  if nil ~= UI.DebugPrintEnabled then
    self._debugPrintEnabled = UI.DebugPrintEnabled()
  end
  self._event2handler = {}
  if self._parent then
    self._root = self._parent._root
    assert(self._root._states[self.name] == nil, "UI fsm '" .. self._root.name .. "': duplicate state name: '" .. self.name .. "'")
    self._root._states[self.name] = self
  else
    self._root = self
    self._machineIndex = hooks.AllocMachineIndex(self)
    self.shared = {}
    assert(nil == fsms[name], "fsm '" .. name .. "' already present")
    fsms[name] = self
    fsmList[1 + #fsmList] = self
    self._states = {}
    self._states[self.name] = self
    self._table0 = {}
    self._table1 = {}
    self._table2 = {}
    self._installedHooks = {}
    self._activeUpdates = 0
    assert(not self.Update)
    self._rootSetup = false
  end
end
function UIState:_lateInit()
  if false == self._initialized then
    self._initialized = true
    self:_activate()
  end
end
function UIState:_incrementActiveUpdates()
  self._activeUpdates = self._activeUpdates + 1
end
function UIState:_decrementActiveUpdates()
  self._activeUpdates = self._activeUpdates - 1
  assert(self._activeUpdates >= 0)
end
function UIState:_scheduleEvent(eventName, fsm, ...)
  local isHook = false
  DeferredEvents[1 + #DeferredEvents] = {
    isHook,
    eventName,
    fsm,
    {
      ...
    }
  }
end
function UIState:_scheduleHookAsEvent(hookName, fsm, ...)
  local isHook = true
  DeferredEvents[1 + #DeferredEvents] = {
    isHook,
    hookName,
    fsm,
    {
      ...
    }
  }
end
function UIState:SendEventUI(eventName, ...)
  local fsmName
  self:_scheduleEvent(eventName, fsmName, unpack({
    ...
  }))
end
function UIState:SendEventToUIFsm(fsmName, eventName, ...)
  self:_scheduleEvent(eventName, fsmName, unpack({
    ...
  }))
end
function UIState:SendHookToUI(hookName, ...)
  assert(not hooks.HookIsPadEvent(hookName), "cannot send pad events through SendHookToUI")
  local fsmName
  self:_scheduleHookAsEvent(hookName, fsmName, unpack({
    ...
  }))
end
function UIState:SendPadEventHookToUIFsm(fsmName, hookName, ...)
  assert(hooks.HookIsPadEvent(hookName), "can only send pad events through SendPadEventHookToUIFSM")
  local fsm = fsms[fsmName]
  self:_scheduleHookAsEvent(hookName .. "." .. fsm:GetMachineIndex(), fsmName, unpack({
    ...
  }))
end
function UIState:AddEventProxy(proxy, priority)
  if nil == priority then
    priority = 0
  end
  if nil == self._eventProxies then
    self._eventProxies = EventProxySet.New()
  end
  self._eventProxies:AddProxy(proxy, priority)
end
function UIState:RemoveEventProxy(proxy)
  self._eventProxies:RemoveProxy(proxy)
end
function UIState:GetMachineIndex()
  return self:GetRoot()._machineIndex
end
function UIState:GetCurrentState()
  local s = self:GetRoot()
  while true do
    local child = s:GetActiveChild()
    if nil == child then
      break
    end
    s = child
  end
  return s
end
function UIState:_getNamespacedStateName(parent, name)
  local root = parent
  while root._parent do
    root = root._parent
  end
  return root.CLASSNAME .. ":" .. name
end
function UIState:StateClass(name, parent)
  local stateClass = classlib.Class(self:_getNamespacedStateName(parent, name), parent)
  stateClass.shortName = name
  return stateClass
end
function UIState:GetState(name)
  local root = self._root
  DoLateInitIfNeeded()
  return root._states[name]
end
function UIState:AddUIElement(uiElement)
  self._uiElements[1 + #self._uiElements] = uiElement
end
function UIState:IsActive()
  return self._active
end
function UIState:WantPadEvents(enabled)
  UI.PadEvents(self:GetMachineIndex(), enabled)
end
function UIState:PadRepeat(rate)
  UI.PadRepeat(self:GetMachineIndex(), rate)
end
function UIState:Enter()
end
function UIState:Exit()
end
function UIState:OnEnterSubstate()
end
function UIState:OnReturnFromSubstate()
end
function UIState:GetParent()
  return self._parent
end
function UIState:_callSetup()
  local setup = self.Setup
  if setup then
    setup(self)
  end
  if self._hierarchy then
    self:_instantiateHierarchy()
  end
  for _, s in ipairs(self._children) do
    s:_callSetup()
  end
end
function UIState:_instantiateHierarchy()
  assert(self._hierarchy, "hierarchy cannot be nil")
  if 0 == #self._hierarchy then
    return
  end
  local stateStack = stack.Stack.New(self)
  local hierarchyLevelStack = stack.Stack.New(self._hierarchy)
  while 0 < stateStack:Size() do
    local s = stateStack:Pop()
    local hl = hierarchyLevelStack:Pop()
    local i = 1
    while i <= #hl do
      local stateClass = hl[i]
      local newChild = s:AddChildByClass(stateClass)
      i = i + 1
      if i <= #hl then
        local nextEntry = hl[i]
        if not classlib.isClass(nextEntry) then
          stateStack:Push(newChild)
          hierarchyLevelStack:Push(nextEntry)
          i = i + 1
        end
      end
    end
  end
end
function UIState:_activate()
  assert(not self._active, "state '" .. self.name .. "' already active")
  if self._parent then
    self._parent:_onEnterSubstate(self)
  end
  if false == self._rootSetup then
    self._rootSetup = true
    self:_callSetup()
    self:_cacheEventHandlers()
  end
  for _, uiElement in ipairs(self._uiElements) do
    uiElement:Reset()
    uiElement:Activate()
  end
  if self.Update then
    self:GetRoot():_incrementActiveUpdates()
    self._registeredUpdate = true
  end
  self._active = true
  assert(tablex.IsEmpty(self._name2timer), "error: timer table not empty (state '" .. self.name .. "')")
  self:Enter()
end
function UIState:_deactivate()
  assert(self._active, "state '" .. self.name .. "' not active")
  self:Exit()
  self._active = false
  if self._registeredUpdate then
    self._registeredUpdate = false
    self:GetRoot():_decrementActiveUpdates()
  end
  for _, timer in pairs(self._name2timer) do
    timer:Stop()
  end
  tablex.Clear(self._name2timer)
  for _, uiElement in ipairs(self._uiElements) do
    uiElement:Deactivate()
  end
  if self._parent then
    self._parent:_onReturnFromSubstate()
  end
end
function UIState:_onEnterSubstate(substate)
  for _, uiElement in ipairs(self._uiElements) do
    if not uiElement:GetActiveInSubstates() then
      uiElement:Deactivate()
    end
  end
  self._activeChild = substate
  self:OnEnterSubstate()
end
function UIState:_onReturnFromSubstate()
  for _, uiElement in ipairs(self._uiElements) do
    if not uiElement:IsActive() then
      uiElement:Activate()
    end
  end
  self._activeChild = nil
  self:OnReturnFromSubstate()
end
function UIState:GetActiveChild()
  return self._activeChild
end
function UIState:AddChild(childState)
  self._children[1 + #self._children] = childState
  childState._parent = self
  childState.shared = self.shared
end
function UIState:AddChildByClass(cls, name)
  if nil == name then
    name = cls.shortName
  end
  local child = cls.New(name, self)
  self:AddChild(child)
  return child
end
function UIState:_update()
  local update = self.Update
  if update then
    engine.ProfileCall("self:Update", update, self)
  end
  local activeChild = self._activeChild
  if activeChild then
    activeChild:_update()
  end
end
function UIState:_cacheEventHandlers()
  local event2handler = self._event2handler
  tablex.Clear(event2handler)
  local cacheEventHandlersFromTable = function(table)
    for key, value in pairs(table) do
      if "function" == type(value) then
        local id = _G.EngineEvents[key]
        local isHandler = id ~= nil
        if not isHandler and "EVT_" == string.sub(key, 1, 4) then
          isHandler = true
        end
        if isHandler then
          local eventName = key
          local handler = value
          event2handler[eventName] = handler
          self:_installHook(eventName)
        end
      end
    end
  end
  cacheEventHandlersFromTable(self)
  local cls = classlib.getClass(self)
  while cls do
    cacheEventHandlersFromTable(cls)
    cls = classlib.getParentClass(cls)
  end
  for _, child in ipairs(self._children) do
    child:_cacheEventHandlers()
  end
end
function UIState:_installHook(hookName)
  local root = self:GetRoot()
  local installed = root._installedHooks[hookName]
  if not installed then
    hooks.InstallWithMachineIndex(hookName, root._machineIndex, root._handleEvent, root, hookName)
    root._installedHooks[hookName] = true
  end
end
function UIState:_handleEvent(evtName, ...)
  if not util.Enabled() then
    return
  end
  local isPad = hooks.HookIsPadEvent(evtName)
  local states = {}
  tablex.Clear(states)
  local s = self
  while s ~= nil do
    states[1 + #states] = s
    s = s:GetActiveChild()
  end
  tablex.Reverse(states)
  local gotoContext = latestGotoContext
  for _, state in ipairs(states) do
    local done = false
    local propogateToOtherStates = true
    local proxyObj, iter
    if state._eventProxies then
      iter = state._eventProxies:GetIter()
      while true do
        local proxyInfo = iter()
        if nil == proxyInfo then
          break
        end
        proxyObj = proxyInfo[1]
        local proxyPriority = proxyInfo[2]
        if 0 < proxyPriority then
          break
        end
        local handler = proxyObj[evtName]
        if nil ~= handler then
          if isPad and GetPadCleared() then
            done = true
            break
          end
          if false == handler(state, ...) then
            done = true
            break
          end
          if gotoContext ~= latestGotoContext then
            done = true
            break
          end
        end
      end
    end
    if not done then
      local handler = state._event2handler[evtName]
      if handler ~= nil then
        if isPad and GetPadCleared() then
          break
        end
        if true ~= handler(state, ...) then
          propogateToOtherStates = false
        end
        if not done and gotoContext ~= latestGotoContext then
          done = true
        end
      end
    end
    if state._eventProxies and not done and proxyObj then
      while true do
        local handler = proxyObj[evtName]
        if nil ~= handler and (isPad and GetPadCleared() or false == handler(state, ...) or gotoContext ~= latestGotoContext) then
          break
        end
        local proxyInfo = iter()
        if nil == proxyInfo then
          break
        end
        proxyObj = proxyInfo[1]
      end
    end
    if not (not done and propogateToOtherStates) then
      break
    end
  end
end
function UIState:IsRoot()
  return nil == self._parent
end
function UIState:GetRoot()
  return self._root
end
function UIState:Goto(stateName)
  assert(stateName ~= nil, "stateName cannot be nil")
  assert(type(stateName) == type(""), "invalid stateName: " .. stateName)
  assert(self:IsActive(), "state '" .. self.name .. "' not active")
  local root = self:GetRoot()
  latestGotoContext = (latestGotoContext + 1) % coreUtil.MaxLuaInt
  local thisGotoContext = latestGotoContext
  local curStack = root._table0
  tablex.Clear(curStack)
  local curStackLen = 0
  do
    local s = root
    while s ~= nil do
      curStackLen = curStackLen + 1
      curStack[curStackLen] = s
      s = s:GetActiveChild()
    end
  end
  local targetStack = root._table1
  tablex.Clear(targetStack)
  local targetStackLen = 0
  do
    local stateStack = root._table2
    tablex.Clear(stateStack)
    stateStack[1] = root
    local stateStackLen = 1
    local s
    local found = false
    while 0 < stateStackLen do
      s = stateStack[stateStackLen]
      if s.name == stateName then
        found = true
        break
      end
      stateStack[stateStackLen] = nil
      stateStackLen = stateStackLen - 1
      for _, substate in ipairs(s._children) do
        stateStackLen = stateStackLen + 1
        stateStack[stateStackLen] = substate
      end
    end
    assert(found, "goto: unknown target state '" .. stateName .. "'")
    local x = s
    while x ~= nil do
      targetStackLen = targetStackLen + 1
      targetStack[targetStackLen] = x
      x = x._parent
    end
    tablex.Reverse(targetStack)
  end
  local n = curStackLen
  while true do
    if targetStackLen >= n and curStack[n].name == targetStack[n].name then
      n = n + 1
      break
    end
    curStack[n]:_deactivate()
    if thisGotoContext ~= latestGotoContext then
      break
    end
    n = n - 1
  end
  if thisGotoContext == latestGotoContext then
    while targetStackLen >= n do
      targetStack[n]:_activate()
      if thisGotoContext ~= latestGotoContext then
        break
      end
      n = n + 1
    end
  end
end
function UIState:DebugPrint(format, ...)
  if self._debugPrintEnabled then
    local stateName = self:GetRoot().name .. ":" .. self.name
    UI.DebugPrint("DebugPrint: State " .. stateName .. " - " .. string.format(format, ...))
  end
end
function UIState:StartTimer(name, duration, callback, ...)
  local timer
  if nil == self._name2timer[name] then
    timer = timerlib.Timer.New(runList, duration, callback, ...)
  else
    timer = self._name2timer[name]
    timer:Reassign(runList, duration, callback, ...)
  end
  self._name2timer[name] = timer
  timer:Start()
end
function UIState:StartEventTimer(name, duration, ...)
  return self:StartTimer(name, duration, function(timer, ...)
    self:HandleEventTimerExpired(name, ...)
  end, ...)
end
function UIState:RestartTimer(name)
  if nil ~= self._name2timer[name] then
    self._name2timer[name]:Restart()
  end
end
function UIState:HandleEventTimerExpired(name, ...)
  self:_handleEvent("EVT_TIMER_EXPIRED", name, ...)
end
function UIState:HaveTimer(name)
  local timer = self._name2timer[name]
  return nil ~= timer and not timer:Done()
end
function UIState:LinkTimerToGamePause(name)
  self._name2timer[name]:LinkToGamePause(true)
end
function UIState:StopTimer(name)
  self._name2timer[name]:Stop()
end
function UIState:FinishTimer(name)
  self._name2timer[name]:Finish()
end
function UIState:DeleteTimer(name)
  local timer = self._name2timer[name]
  if nil ~= timer then
    timer:Stop()
    self._name2timer[name] = nil
  end
end
function UIState:PauseTimer(name)
  self._name2timer[name]:Pause()
end
function UIState:UnpauseTimer(name)
  self._name2timer[name]:Unpause()
end
function UIState:QueryTimer(name)
  return self._name2timer[name]:GetElapsedTime()
end
function UIState:GetTimerRemainingTime(name)
  return self._name2timer[name]:GetRemainingTime()
end
function UIState:AddTimeToTimer(name, dt)
  return self._name2timer[name]:Update(dt)
end
local builtinStrings = {__identity = true, name = true}
function UIState:_GetScriptVar(stateName, varName, index)
  local GetVar = function(table)
    local var = table[varName]
    if nil ~= var then
      if 0 <= index then
        return var[index]
      else
        return var
      end
    end
    return nil
  end
  local result
  if self:IsActive() then
    local state = self._root._states[stateName]
    if state then
      result = GetVar(state)
      if nil == result and state:IsRoot() then
        result = GetVar(self.shared)
      end
    end
  end
  return result
end
function UIState:_GetScriptVarSliType(stateName, varName, index)
  local var = self:_GetScriptVar(stateName, varName, index)
  return util.GetSliType(var)
end
function UIState:_publishStateVars(machineName)
  engine.PushProfileMarker("_publishStateVars")
  local publishStateFromTable = function(table, ignoredNames)
    for name, value in pairs(table) do
      local valueType = type(value)
      local isString = "string" == valueType
      local isTable = "table" == valueType
      local varName
      if isString or isTable then
        varName = machineName .. ":" .. self.name .. ":" .. name
      end
      if isString then
        if not ignoredNames or nil == builtinStrings[name] then
          engine.ProfileCall("util.PublishValue", util.PublishValue, varName, value)
        end
      elseif "table" == valueType then
        local i = 1
        while true do
          local indexedValue = value[i]
          if indexedValue == nil or "string" ~= type(indexedValue) or ignoredNames and nil ~= builtinStrings[name] then
            break
          end
          do
            local indexedVarName = varName .. "[" .. i .. "]"
            engine.ProfileCall("util.PublishValue", util.PublishValue, indexedVarName, indexedValue)
          end
          i = i + 1
        end
      end
    end
  end
  publishStateFromTable(self, builtinStrings)
  if self:IsRoot() then
    publishStateFromTable(self.shared)
  end
  engine.PopProfileMarker()
end
function UIState:_publishState()
  if nil == UI.ResetStateVars then
    return
  end
  assert(self:IsActive(), "_publishState called on non-active state '" .. self.name .. "'")
  assert(self:IsRoot(), "_publishState called on non-root state '" .. self.name .. "'")
  local states = {}
  states[1] = self
  while 0 < #states do
    local s = states[#states]
    states[#states] = nil
    s:_publishStateVars(self.name)
    for _, child in ipairs(s._children) do
      states[1 + #states] = child
    end
  end
end
function UIState:OnSaveCheckpoint(tab)
  assert(self:IsRoot(), "OnSaveCheckpoint called on non-root state '" .. self.name .. "'")
end
function UIState:OnRestoreCheckpoint(tab)
  assert(self:IsRoot(), "OnRestoreCheckpoint called on non-root state '" .. self.name .. "'")
end
local HandleEvent = function(name, ...)
  DoLateInitIfNeeded()
  for _, fsm in ipairs(fsmList) do
    fsm:_handleEvent(name, ...)
  end
end
local HandleRestart = function()
  HandleEvent("EVT_Restart")
end
local HandleGameQuit = function()
  HandleEvent("EVT_GAME_OVER")
end
local Update = function(dt)
  ResetPadClear()
  DoLateInitIfNeeded()
  for _, fsm in ipairs(fsmList) do
    if fsm._activeUpdates > 0 then
      engine.ProfileCall("fsm:_update", fsm._update, fsm)
    end
  end
  engine.PushProfileMarker("handleEvents")
  local maxIterations = 3
  for _ = 1, maxIterations do
    if #DeferredEvents == 0 then
      break
    end
    local defEvts = clone.shallow(DeferredEvents)
    tablex.Clear(DeferredEvents)
    for _, evt in ipairs(defEvts) do
      local isHook = evt[1]
      local evtName = evt[2]
      local fsmName = evt[3]
      local args = evt[4]
      if isHook then
        hooks.SimulateHook(evtName, unpack(args))
      elseif fsmName == nil then
        HandleEvent(evtName, unpack(args))
      else
        local fsm = GetFSMByName(fsmName)
        if fsm ~= nil then
          fsm:_handleEvent(evtName, unpack(args))
        end
      end
    end
  end
  engine.PopProfileMarker()
  engine.ProfileCall("runList:Update", runList.Update, runList, dt, game.IsPaused())
  if nil ~= UI.ResetStateVars then
    engine.PushProfileMarker("_publishState")
    for _, fsm in ipairs(fsmList) do
      fsm:_publishState()
    end
    engine.PopProfileMarker()
  end
end
local GetScriptVarSliType = function(...)
  local _, machineName, stateName, varName, index
  if select("#", ...) == 5 then
    _, machineName, stateName, varName, index = ...
  else
    machineName, stateName, varName, index = ...
  end
  for _, fsm in ipairs(fsmList) do
    if machineName == fsm.name then
      return fsm:_GetScriptVarSliType(stateName, varName, index)
    end
  end
  return util.SLI_UNKNOWN_TYPE
end
local GetScriptVar = function(level, machineName, stateName, varName, index)
  if index == nil then
    local fsm = fsms[level]
    if fsm then
      local result = fsm:_GetScriptVar(machineName, stateName, varName)
      return result
    end
    return nil
  end
  local fsm = fsms[machineName]
  if fsm then
    local result
    if fsm._active then
      local state = fsm._root._states[stateName]
      if state then
        local var = state[varName]
        if var then
          if 0 <= index then
            result = var[index]
          else
            result = var
          end
        end
        if nil == result and state._parent == nil then
          var = fsm.shared[varName]
          if var then
            if 0 <= index then
              result = var[index]
            else
              result = var
            end
          end
        end
      end
    end
    return result
  end
  return nil
end
local OnSaveCheckpoint = function(tab)
  local fsmTab = {}
  tab.fsm = fsmTab
  for _, fsm in ipairs(fsmList) do
    local t = {}
    fsmTab[fsm.name] = t
    fsm:OnSaveCheckpoint(t)
  end
end
local OnRestoreCheckpoint = function(tab)
  local fsmTab = tab.fsm
  if fsmTab then
    DoLateInitIfNeeded()
    for _, fsm in ipairs(fsmList) do
      local t = fsmTab[fsm.name]
      fsm:OnRestoreCheckpoint(t)
    end
  end
end
if not game.UI.EnumerateScripterDefinedEventName then
  local HandleSendHookToMachine = function(evtName, machineIndex)
    local fsm = hooks.GetFSMAtMachineIndex(machineIndex)
    fsm:_handleEvent(evtName)
  end
  thunk.Install("SendHookToMachine", function(_, ...)
    HandleSendHookToMachine(...)
  end)
end
local HandleGetMachineAndStateName = function(index)
  _G.MachineName = ""
  _G.MachineIndex = 0
  _G.StateName = ""
  local fsmNames = tablex.Keys(fsms)
  table.sort(fsmNames)
  local fsmName = fsmNames[index + 1]
  local fsm = fsms[fsmName]
  if fsm then
    _G.MachineName = fsm.name
    _G.MachineIndex = fsm._machineIndex
    _G.StateName = fsm:GetCurrentState().name
  end
end
thunk.Install("GetMachineAndStateName", function(_, ...)
  HandleGetMachineAndStateName(...)
end)
return {
  GetScriptVarSliType = GetScriptVarSliType,
  GetScriptVar = GetScriptVar,
  GetFSMByName = GetFSMByName,
  UIState = UIState,
  OnSaveCheckpoint = OnSaveCheckpoint,
  OnRestoreCheckpoint = OnRestoreCheckpoint,
  Update = Update,
  HandleEvent = HandleEvent,
  HandleRestart = HandleRestart,
  HandleGameQuit = HandleGameQuit,
  runList = runList
}
