#!/usr/bin/lua

-- Created by jiaqiang.ye@lnxall.com
-- Coroutine based task list for Lua5.1
-- 2023/01/03

local invoker = require "invoker"

local tsklua = {}

tsklua["tasklist"] = {}

tsklua["taskadd"] = function (func, restart)
    local typn = type(func)
    if typn ~= "function" then
        io.stderr:write("Error, function not given.\n")
        io.stderr:flush()
        return false
    end
    local cofun = coroutine.create(func)
    typn = type(cofun)
    if typn ~= "thread" then
        io.stderr:write("Error, failed to create coroutine.\n")
        io.stderr:flush()
        return false
    end

    local tlist = tsklua["tasklist"]
    tlist[#tlist + 1] = {
        ["cfun"] = cofun,
        ["ofunc"] = func,
        ["expire"] = invoker.uptimsec(),
        ["restart"] = restart and true or false,
    }
    tsklua["tasklist"] = tlist
    return #tlist
end

local function pick_next_task(tsklist, nowt)
    local msecond = 0x7FFFFFFF
    if not nowt then nowt = invoker.uptimsec() end
    for idx, tsk in ipairs(tsklist) do
        local sta = coroutine.status(tsk["cfun"])
        if sta == "dead" then
            if tsk["restart"] and type(tsk["ofunc"]) == "function" then
                tsk["cfun"] = coroutine.create(tsk["ofunc"])
                if nowt > tsk["expire"] then tsk["expire"] = nowt end
            end
        else
            if tsk["expire"] <= nowt then return idx end
            local ms = tsk["expire"] - nowt
            if ms > 0 and ms < msecond then msecond = ms end
        end
    end
    if msecond == 0x7FFFFFFF then msecond = 10000 end
    return false, math.floor(msecond)
end

tsklua["taskloop"] = function (howl, dfunc, funcdata)
    local nowt = invoker.uptimsec()
    local initim = nowt
    local tlist = tsklua["tasklist"]
    if type(dfunc) ~= "function" then dfunc = nil end
    while true do
        local tdx, mdel = pick_next_task(tlist, nowt)
        if not tdx then
            -- no task runnable, just sleep enough time
            if dfunc then
                dfunc(mdel, funcdata)
            else
                invoker.msleep(mdel)
            end
            nowt = invoker.uptimsec()
        else
            local tsk = tlist[tdx]
            local okay, mnext = coroutine.resume(tsk["cfun"], tdx)
            if not okay then
                io.stderr:write("Error, failed to resume coroutine.\n")
                io.stderr:flush()
            end
            nowt = invoker.uptimsec()
            local typn = type(mnext)
            if typn ~= "number" or mnext <= 0 then
                mnext = 30000 -- force task to delay 30 seconds
            else
                mnext = math.floor(mnext)
            end
            tsk["expire"] = nowt + mnext
        end
        tlist = tsklua["tasklist"]
        if howl and type(howl) == "number" and nowt >= (initim + howl) then
            -- time up, break out of task loop
            break
        end
    end
    return true
end

tsklua["delay"] = function (msec)
    local idx = coroutine.yield(msec)
    return idx
end

return tsklua
