local io = assert(io)
local pairs = assert(pairs)
local select = assert(select)
local string_format = assert(string.format)
local tostring = assert(tostring)
local type = assert(type)

local print_id = 0
local P = function(whence_, ...)
    if not io then return end
    print_id = print_id + 1
    local _str = string_format("%s: %02d.\t", whence_, print_id)
    for _i  = 1, select('#', ...) do
        _str = _str .. tostring(select(_i, ...)) .. "\t"
    end
    if io then
        io.stderr:write(_str .. "\n")
    end
end

local MAKE_PRINT = function()
    local _whence = lane_threadname and lane_threadname() or "main"
    return function(...)
        P(_whence, ...)
    end
end

local tables_match

-- true if 'a' is a subtable of 'b'
--
local function subtable(a, b)
    --
    assert(type(a)=="table" and type(b)=="table")

    for k,v in pairs(b) do
        if type(v)~=type(a[k]) then
            return false    -- not subtable (different types, or missing key)
        elseif type(v)=="table" then
            if not tables_match(v,a[k]) then return false end
        else
            if a[k] ~= v then return false end
        end
    end
    return true     -- is a subtable
end

-- true when contents of 'a' and 'b' are identical
--
tables_match = function(a, b)
    return subtable(a, b) and subtable(b, a)
end

local function dump_error_stack(error_reporting_mode_, stack)
    local PRINT = MAKE_PRINT()
    if error_reporting_mode_ == "minimal" then
        assert(stack == nil, table.concat{"stack is a ", type(stack)})
    elseif error_reporting_mode_ == "basic" then -- each stack line is a string in basic mode
        PRINT("STACK:\n", table.concat(stack,"\n\t"));
    else -- each stack line is a table in extended mode
        PRINT "STACK:"
        for line, details in pairs(stack) do
            PRINT("\t", line);
            for detail, value in pairs(details) do
                PRINT("\t\t", detail, ": ", value)
            end
        end
    end
end

-- a function that yields back what got in, one element at a time
local yield_one_by_one = function(...)
    local PRINT = MAKE_PRINT()
    PRINT "In lane"
    for _i  = 1, select('#', ...) do
        local _val = select(_i, ...)
        PRINT("yielding #", _i, _val)
        local _ack = coroutine.yield(_val)
        if cancel_test and cancel_test() then -- cancel_test does not exist when run immediately (not in a Lane)
            return "cancelled!"
        end
        -- of course, if we are cancelled, we were not resumed, and yield() didn't return what we expect
        assert(_ack == _i)
    end
    return "bye!"
end

return {
    MAKE_PRINT = MAKE_PRINT,
    tables_match = tables_match,
    dump_error_stack = dump_error_stack,
    yield_one_by_one = yield_one_by_one
}
