-- Due the coroutines this module can't be reloaded safely
-- It should handle re-registering fine however
NO_RELOAD = true

local staff_data = serialize.define({
    {"object_index", "u16"}
})

local action_handlers = {}

function register(obj, handler, chandler, weight)
    if action_handlers[obj] ~= nil then
        print(tostring(obj) .. " already has a handler, re-registering")
    end
    action_handlers[obj] = {
        handler = handler,
        client_handler = chandler,
        weight = weight,
    }
end

local function handler_for(obj)
    return action_handlers[obj]
end

local function ticker_func(default_func, default_weight)
    while true do
        local room = get_room()
        local options = {}
        local total_weight = 0.0
        if default_weight > 0.0 then
            total_weight = total_weight + default_weight
            table.insert(options, {handler=default_func, weight=default_weight})
        end
        for idx, obj in ipairs(room.objects) do
            local key = obj.key
            local handler = handler_for(key)
            if handler ~= nil then
                total_weight = total_weight + handler.weight
                table.insert(options, {
                    idx = idx,
                    handler = handler.handler,
                    weight = handler.weight,
                    object = obj,
                })
            end
        end
        if #options ~= 0 then
            local selection = math.random() * total_weight
            local offset = 0.0
            for _, wo in ipairs(options) do
                if selection < offset + wo.weight then
                    local e = get_entity()
                    e.properties["object_index"] = wo.idx
                    wo.handler(wo.object)
                    break
                else
                    offset = offset + wo.weight
                end
            end
        end
        wait()
    end
end

local GET_ROOM_FLAG = 1
local GET_ENTITY_FLAG = 2

function tick_staff(room, e, default_func, default_weight)
    local ticker = e.properties['ticker']
    if ticker == nil or coroutine.status(ticker) == "dead" then
        ticker = coroutine.create(function()
            ticker_func(default_func, default_weight)
        end)
        e.properties['ticker'] = ticker
    end

    local param
    while true do
        local ok, val = coroutine.resume(ticker, param)
        param = nil
        if not ok then
            error(val)
        end
        if val == GET_ROOM_FLAG then
            param = room
        elseif val == GET_ENTITY_FLAG then
            param = e
        else
            break
        end
    end
end

function tick_staff_client(room, entities)
    local objects = room.objects
    for _, e in ipairs(entities) do
        if e.state ~= nil and e.key ~= "base:student" then
            if e.state ~= nil then
                local data = staff_data.decode(e.state)
                local obj = objects[data.object_index]
                local handler = handler_for(obj.key)
                if handler ~= nil then
                    handler.client_handler(e, obj)
                end
            end
        end
    end
end

-- Ticker functions

-- Waits a tick
function wait()
    coroutine.yield()
end

-- Access to a non-stale room reference
function get_room()
    return coroutine.yield(GET_ROOM_FLAG)
end

-- Access to a non-stale entity reference
function get_entity()
    return coroutine.yield(GET_ENTITY_FLAG)
end

-- Sets the entity's current state (to be sync'd to
-- the client
function set_state(enc, new_state)
    if enc == nil then
        enc = staff_data
        new_state = {}
    end
    local e = get_entity();
    new_state.object_index = e.properties.object_index;
    e.state = enc.encode(new_state)
end