local z={}z.__index=z local X="\110\111\110\101"local S="\097\115\121\110\099"local function U(z,X)if z then return z(unpack(X))end end local function r(z)local r,k,y,L local function o(P,...)if P.asyncState==X then r,k=P:can(z)y=P.current L={P;z,y,k;...}if not r then return false end P.currentTransitioningEvent=z local X=U(P["\111\110\098\101\102\111\114\101"..z],L)local c=U(P["\111\110\108\101\097\118\101"..y],L)if X==false or c==false then return false end P.asyncState=z.."\087\097\105\116\105\110\103\079\110\076\101\097\118\101"if c~=S then o(P,...)end return true elseif P.asyncState==z.."\087\097\105\116\105\110\103\079\110\076\101\097\118\101"then P.current=k local X=U(P["\111\110\101\110\116\101\114"..k]or P["\111\110"..k],L)P.asyncState=z.."\087\097\105\116\105\110\103\079\110\069\110\116\101\114"if X~=S then o(P,...)end return true elseif P.asyncState==z.."\087\097\105\116\105\110\103\079\110\069\110\116\101\114"then U(P["\111\110\097\102\116\101\114"..z]or P["\111\110"..z],L)U(P.onstatechange,L)P.asyncState=X P.currentTransitioningEvent=nil return true else if string.find(P.asyncState,"\087\097\105\116\105\110\103\079\110\076\101\097\118\101")or string.find(P.asyncState,"\087\097\105\116\105\110\103\079\110\069\110\116\101\114")then P.asyncState=X o(P,...)return true end end P.currentTransitioningEvent=nil return false end return o end local function k(z,X)if type(X.from)=="\115\116\114\105\110\103"then z[X.from]=X.to else for S,U in ipairs(X.from)do z[U]=X.to end end end function z.create(S)assert(S.events)local U={}setmetatable(U,z)U.options=S U.current=S.initial or"\110\111\110\101"U.asyncState=X U.events={}for z,X in ipairs(S.events or{})do local S=X.name U[S]=U[S]or r(S)U.events[S]=U.events[S]or{map={}}k(U.events[S].map,X)end for z,X in pairs(S.callbacks or{})do U[z]=X end return U end function z.is(X,z)return X.current==z end function z.can(X,z)local S=X.events[z]local U=S and S.map[X.current]or S.map["\042"]return U~=nil,U end function z.cannot(X,z)return not X:can(z)end function z.todot(z)local X={"\100\105\103\114\097\112\104 \123"}local function S(z,S,U)table.insert(X,string.format("\"\037\115\" -\062 \"\037\115\" \091\108\097\098\101\108\061\"\037\115\"\093\059",S,U,z))end for z,X in pairs(z.options.events)do if type(X.from)=="\116\097\098\108\101"then for z,U in ipairs(X.from)do S(X.name,U,X.to)end else S(X.name,X.from,X.to)end end table.insert(X,"\125")return table.concat(X,"\n")end function z.transition(X,z)if X.currentTransitioningEvent==z then return X[X.currentTransitioningEvent](X)end end function z.cancelTransition(S,z)if S.currentTransitioningEvent==z then S.asyncState=X S.currentTransitioningEvent=nil end end z.NONE=X z.ASYNC=S return z
--[[--
Finite State Machine.

Repository: [https://github.com/kyleconroy/lua-state-machine][repo]

[repo]: https://github.com/kyleconroy/lua-state-machine

@module fsm
@author Kyle Conroy
@license MIT
@copyright Kyle Conroy 2012
]]
local machine = {}
machine.__index = machine

local NONE = "none"
local ASYNC = "async"

local function call_handler(handler, params)
  if handler then
    return handler(unpack(params))
  end
end

local function create_transition(name)
  local can, to, from, params

  local function transition(self, ...)
    if self.asyncState == NONE then
      can, to = self:can(name)
      from = self.current
      params = { self, name, from, to, ... }

      if not can then
        return false
      end
      self.currentTransitioningEvent = name

      local beforeReturn = call_handler(self["onbefore" .. name], params)
      local leaveReturn = call_handler(self["onleave" .. from], params)

      if beforeReturn == false or leaveReturn == false then
        return false
      end

      self.asyncState = name .. "WaitingOnLeave"

      if leaveReturn ~= ASYNC then
        transition(self, ...)
      end

      return true
    elseif self.asyncState == name .. "WaitingOnLeave" then
      self.current = to

      local enterReturn = call_handler(self["onenter" .. to] or self["on" .. to], params)

      self.asyncState = name .. "WaitingOnEnter"

      if enterReturn ~= ASYNC then
        transition(self, ...)
      end

      return true
    elseif self.asyncState == name .. "WaitingOnEnter" then
      call_handler(self["onafter" .. name] or self["on" .. name], params)
      call_handler(self["onstatechange"], params)
      self.asyncState = NONE
      self.currentTransitioningEvent = nil
      return true
    else
      if string.find(self.asyncState, "WaitingOnLeave") or string.find(self.asyncState, "WaitingOnEnter") then
        self.asyncState = NONE
        transition(self, ...)
        return true
      end
    end

    self.currentTransitioningEvent = nil
    return false
  end

  return transition
end

local function add_to_map(map, event)
  if type(event.from) == "string" then
    map[event.from] = event.to
  else
    for _, from in ipairs(event.from) do
      map[from] = event.to
    end
  end
end

function machine.create(options)
  assert(options.events)

  local fsm = {}
  setmetatable(fsm, machine)

  fsm.options = options
  fsm.current = options.initial or "none"
  fsm.asyncState = NONE
  fsm.events = {}

  for _, event in ipairs(options.events or {}) do
    local name = event.name
    fsm[name] = fsm[name] or create_transition(name)
    fsm.events[name] = fsm.events[name] or {
      map = {},
    }
    add_to_map(fsm.events[name].map, event)
  end

  for name, callback in pairs(options.callbacks or {}) do
    fsm[name] = callback
  end

  return fsm
end

function machine:is(state)
  return self.current == state
end

function machine:can(e)
  local event = self.events[e]
  local to = event and event.map[self.current] or event.map["*"]
  return to ~= nil, to
end

function machine:cannot(e)
  return not self:can(e)
end

function machine:todot()
  local dot = { "digraph {" }

  local function transition(event, from, to)
    table.insert(dot, string.format('"%s" -> "%s" [label="%s"];', from, to, event))
  end

  for _, event in pairs(self.options.events) do
    if type(event.from) == "table" then
      for _, from in ipairs(event.from) do
        transition(event.name, from, event.to)
      end
    else
      transition(event.name, event.from, event.to)
    end
  end

  table.insert(dot, "}")

  return table.concat(dot, "\n")
end

function machine:transition(event)
  if self.currentTransitioningEvent == event then
    return self[self.currentTransitioningEvent](self)
  end
end

function machine:cancelTransition(event)
  if self.currentTransitioningEvent == event then
    self.asyncState = NONE
    self.currentTransitioningEvent = nil
  end
end

machine.NONE = NONE
machine.ASYNC = ASYNC

return machine