-------------------------------------------------------------------------------
--- Olivine-Labs Busted-Done
-------------------------------------------------------------------------------

local M = {}

-- adds tokens to the current wait list, does not change order/unordered
M.wait = function(self, ...)
    local tlist = { ... }

    for _, token in ipairs(tlist) do
        if type(token) ~= "string" then
            error("Wait tokens must be strings. Got "..type(token), 2)
        end
        table.insert(self.tokens, token)
    end
end

-- set list as unordered, adds tokens to current wait list
M.wait_unordered = function(self, ...)
    self.ordered = false
    self:wait(...)
end

-- set list as ordered, adds tokens to current wait list
M.wait_ordered = function(self, ...)
    self.ordered = true
    self:wait(...)
end

-- generates a message listing tokens received/open
M.tokenlist = function(self)
    local list

    if #self.tokens_done == 0 then
        list = "No tokens received."
    else
        list = "Tokens received ("..tostring(#self.tokens_done)..")"
        local s = ": "

        for _,t in ipairs(self.tokens_done) do
            list = list .. s .. "'"..t.."'"
            s = ", "
        end

        list = list .. "."
    end

    if #self.tokens == 0 then
        list = list .. " No more tokens expected."
    else
        list = list .. " Tokens not received ("..tostring(#self.tokens)..")"
        local s = ": "

        for _, t in ipairs(self.tokens) do
            list = list .. s .. "'"..t.."'"
            s = ", "
        end

        list = list .. "."
    end

    return list
end

-- marks a token as completed, checks for ordered/unordered, checks for completeness
M.done = function(self, ...) self:_done(...) end  -- extra wrapper for same error level constant as __call method
M._done = function(self, token)
    if token then
        if type(token) ~= "string" then
            error("Wait tokens must be strings. Got "..type(token), 3)
        end

        if self.ordered then
            if self.tokens[1] == token then
                table.remove(self.tokens, 1)
                table.insert(self.tokens_done, token)
            else
                if self.tokens[1] then
                    error(("Bad token, expected '%s' got '%s'. %s"):format(self.tokens[1], token, self:tokenlist()), 3)
                else
                    error(("Bad token (no more tokens expected) got '%s'. %s"):format(token, self:tokenlist()), 3)
                end
            end
        else
            -- unordered
            for i, t in ipairs(self.tokens) do
                if t == token then
                    table.remove(self.tokens, i)
                    table.insert(self.tokens_done, token)
                    token = nil
                    break
                end
            end

            if token then
                error(("Unknown token '%s'. %s"):format(token, self:tokenlist()), 3)
            end
        end
    end
    if not next(self.tokens) then
        -- no more tokens, so we're really done...
        self.done_cb()
    end
end


-- wraps a done callback into a done-object supporting tokens to sign-off
M.new = function(done_callback)
    local obj = {
        tokens = {},
        tokens_done = {},
        done_cb = done_callback,
        ordered = true,  -- default for sign off of tokens
    }

    return setmetatable( obj, {
        __call = function(self, ...)
            self:_done(...)
        end,
        __index = M,
    })
end

return M
