local g_call_state = {
  args = {},
  M = nil,
  active_state = nil,
  executing_state = nil
}
_G.__statemachine_call_state__ = g_call_state
local call_state = function()
  local CS = g_call_state
  assert(CS.M)
  return CS
end
local call_start = function(M, A, E, ...)
  local CS = g_call_state
  assert(not CS.M)
  CS.args[1], CS.args[2], CS.args[3], CS.args[4], CS.args[5] = ...
  CS.active_state = A
  CS.executing_state = E
  CS.cancel_current = nil
  CS.M = M
  return CS
end
local call_end = function(CS)
  assert(CS.M)
  CS.args[1], CS.args[2], CS.args[3], CS.args[4], CS.args[5] = nil, nil, nil, nil, nil
  CS.M = nil
  CS.active_state = nil
  CS.executing_state = nil
  CS.cancel_current = nil
end
local crash_cleanup = function()
  local CS = g_call_state
  if CS.M then
    call_end(CS)
  end
end
local dispatch_multiple_function = function(states, function_name, ...)
  local CS = call_state()
  local prev_executing = CS.executing_state
  for _, v in pairs(states) do
    local fn = v[function_name]
    if fn then
      CS.executing_state = v
      engine.ProfileCall(fn, v, ...)
    end
  end
  CS.executing_state = prev_executing
end
local _dispatch_single_event = function(state, event_set, event_name, ...)
  local CS = call_state()
  local events = state[event_set]
  local event = events and events[event_name]
  if event then
    local prev_executing = CS.executing_state
    CS.executing_state = state
    local result = engine.ProfileCall(event, state, ...)
    CS.executing_state = prev_executing
    return result
  end
end
local _dispatch_multiple_event_except = function(states, except_state, event_set, event_name, ...)
  local handled
  for _, v in pairs(states) do
    if v ~= except_state then
      local result = _dispatch_single_event(v, event_set, event_name, ...)
      handled = handled or result
    end
  end
  return handled
end
local active_dispatch_event = function(M, active_state, event_set, event_name, ...)
  local handled
  if active_state then
    handled = _dispatch_single_event(active_state, event_set, event_name, ...)
  end
  handled = handled or _dispatch_multiple_event_except(M.States, active_state, event_set, event_name, ...)
  return handled
end
local get_state_instance = function(M, name)
  local key = "__statemachine_private__"
  M[key] = M[key] or {}
  M[key][name] = M[key][name] or {
    transient = {}
  }
  return M[key][name]
end
local get_global_instance = function(M)
  local key = "__statemachine_private__"
  local global = "__global__"
  M[key] = M[key] or {}
  M[key][global] = M[key][global] or {}
  return M[key][global]
end
local update = function(M, active_state, selector_function, ...)
  local CS = call_start(M, active_state, nil, ...)
  local global_data_update = M.UpdateData
  if global_data_update then
    engine.ProfileCall("UpdateData", global_data_update, M, ...)
  end
  dispatch_multiple_function(M.States, "UpdateData", ...)
  local new_state_class = selector_function and engine.ProfileCall(selector_function, M, ...)
  local new_state = new_state_class and M.States[new_state_class.Name]
  local state_changed = new_state ~= active_state
  local doExit = state_changed and active_state
  local doEnter = state_changed and new_state
  local doUpdate = new_state
  if doExit then
    local fn = active_state.exit_thunk or active_state.Exit
    CS.executing_state = active_state
    if fn then
      engine.ProfileCall(fn, active_state, ...)
    end
    get_state_instance(M, active_state.Name).transient = {}
  end
  if doEnter then
    local fn = new_state.enter_thunk or new_state.Enter
    CS.executing_state = new_state
    CS.active_state = new_state
    if fn then
      engine.ProfileCall(fn, new_state, ...)
    end
  end
  if doUpdate then
    local fn = new_state.update_thunk or new_state.Update
    CS.executing_state = new_state
    CS.active_state = new_state
    if fn then
      engine.ProfileCall(fn, new_state, ...)
      if CS.cancel_current then
        get_state_instance(M, new_state.Name).transient = {}
        new_state = nil
      end
    end
  end
  call_end(CS)
  return new_state
end
return {
  update = update,
  active_dispatch_event = active_dispatch_event,
  dispatch_multiple_function = dispatch_multiple_function,
  call_state = call_state,
  call_start = call_start,
  call_end = call_end,
  crash_cleanup = crash_cleanup,
  get_state_instance = get_state_instance,
  get_global_instance = get_global_instance
}
