
-- Multiple Finite State Machine
mfsm = {}

-- runs every time you change stuff, to make sure your entity is set up correctly
mfsm.init_states = function(self)
    if self._mfsm_has_init then return end
    self._mfsm_active_states = {}
    self._mfsm_state_meta = {}
    self._mfsm_state_map = {}
    self._mfsm_states = self._mfsm_states or {}
    for i, state in ipairs(self._mfsm_states) do
        self._mfsm_state_map[state.name] = state
    end
    self._mfsm_has_init = true
end

mfsm.get_state_meta = function(self, state_name)
    local meta = self._mfsm_state_meta[state_name]
    if not meta then meta = {}; self._mfsm_state_meta[state_name] = meta end
    return meta
end
-- do the state code, based on functype e.g. on_step or on_end
mfsm.do_state = function(self, state_name, functype, ...)
    if self._mfsm_state_map[state_name] and self._mfsm_state_map[state_name][functype] then
        self._mfsm_state_map[state_name][functype](self, ...)
    end
end
-- set a single state and trigger its on_start if it isn't already active
mfsm.set_state = function(self, state_name, val, exclusive)
    mfsm.init_states(self)
    -- drop other states if flag set
    if exclusive then
        mfsm.reset_all_states(self, {state_name=true})
    end
    local has_state = self._mfsm_active_states[state_name]
    val = (val==true) -- convert to only bool
    local meta = mfsm.get_state_meta(self, state_name)
    if has_state and not val then
        mfsm.do_state(self, state_name, "on_end", meta)
        self._mfsm_state_meta[state_name] = nil
        self._mfsm_active_states[state_name] = val
    elseif val and not has_state then
        mfsm.do_state(self, state_name, "on_start", meta)
        self._mfsm_state_meta[state_name] = {
            state_time = 0
        }
        self._mfsm_active_states[state_name] = val
    end
end
-- set a map of states to their given values
mfsm.set_states = function(self, states, exclusive)
    mfsm.init_states(self)
    -- drop other states if flag set
    if exclusive then
        mfsm.reset_all_states(self, states)
    end
    -- set all states to their respective values
    for state_name, val in pairs(states) do
        mfsm.set_state(self, state_name, val)
    end
end
-- put this in your on_step of your entity
mfsm.on_step = function(self, dtime)
    mfsm.init_states(self)
    for i, state in ipairs(self._mfsm_states) do
        if self._mfsm_active_states[state.name] then
            local meta = mfsm.get_state_meta(self, state.name)
            mfsm.do_state(self, state.name, "on_step", dtime, meta)
            meta.state_time = meta.state_time + dtime
        end
    end
end
-- removes all active states
mfsm.reset_all_states = function(self, exclude_list)
    if not exclude_list then exclude_list = {} end
    for state_name, is_active in pairs(self._mfsm_active_states) do
        local state = self._mfsm_state_map[state_name] or {}
        if (is_active == true) and (exclude_list[state_name] == nil)
        and not state.is_protected then
            mfsm.set_state(self, state_name, false)
        end
    end
end

mfsm.__meta = {__index = mfsm}
