if not modules then modules = { } end modules ['node-tsk'] = {
    version   = 1.001,
    comment   = "companion to node-ini.mkiv",
    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
    copyright = "PRAGMA ADE / ConTeXt Development Team",
    license   = "see context related readme files"
}

-- This might move to task-* and become less code as in sequencers
-- we already have dirty flags as well. On the other hand, nodes are
-- rather specialized and here we focus on node related tasks.

local format = string.format

local trace_tasks = false  trackers.register("tasks.creation", function(v) trace_tasks = v end)

local report_tasks  = logs.reporter("tasks")

local allocate      = utilities.storage.allocate

local context       = context
local nodes         = nodes

local tasks         = nodes.tasks or { }
nodes.tasks         = tasks

local tasksdata     = { } -- no longer public

local sequencers    = utilities.sequencers
local compile       = sequencers.compile
local nodeprocessor = sequencers.nodeprocessor

local newsequencer  = sequencers.new

local appendgroup   = sequencers.appendgroup
----- prependgroup  = sequencers.prependgroup
----- replacegroup  = sequencers.replacegroup
local enablegroup   = sequencers.enablegroup
local disablegroup  = sequencers.disablegroup

local appendaction  = sequencers.appendaction
local prependaction = sequencers.prependaction
local replaceaction = sequencers.replaceaction
local enableaction  = sequencers.enableaction
local disableaction = sequencers.disableaction

local frozengroups  = "no"

function tasks.freeze(kind)
    frozengroups = kind or "tolerant" -- todo: hook into jobname
end

function tasks.new(specification) -- was: name,arguments,list
    local name      = specification.name
    local arguments = specification.arguments or 0
    local sequence  = specification.sequence
    if name and sequence then
        local tasklist = newsequencer {
            -- we can move more to the sequencer now .. todo
        }
        tasksdata[name] = {
            list      = tasklist,
            runner    = false,
            arguments = arguments,
         -- sequence  = sequence,
            frozen    = { },
            processor = specification.processor or nodeprocessor
        }
        for l=1,#sequence do
            appendgroup(tasklist,sequence[l])
        end
    end
end

local function valid(name)
    local data = tasksdata[name]
    if not data then
        report_tasks("unknown task %a",name)
    else
        return data
    end
end

local function validgroup(name,group,what)
    local data = tasksdata[name]
    if not data then
        report_tasks("unknown task %a",name)
    else
        local frozen = data.frozen[group]
        if frozen then
            if frozengroup == "no" then
                -- default
            elseif frozengroup == "strict" then
                report_tasks("warning: group %a of task %a is frozen, %a applied but not supported",group,name,what)
                return
            else -- if frozengroup == "tolerant" then
                report_tasks("warning: group %a of task %a is frozen, %a ignored",group,name,what)
            end
        end
        return data
    end
end

function tasks.freezegroup(name,group)
    local data = valid(name)
    if data then
        data.frozen[group] = true
    end
end

function tasks.restart(name)
    local data = valid(name)
    if data then
        data.runner = false
    end
end

function tasks.enableaction(name,action)
    local data = valid(name)
    if data then
        enableaction(data.list,action)
        data.runner = false
    end
end

function tasks.disableaction(name,action)
    local data = valid(name)
    if data then
        disableaction(data.list,action)
        data.runner = false
    end
end

function tasks.replaceaction(name,group,oldaction,newaction)
    local data = valid(name)
    if data then
        replaceaction(data.list,group,oldaction,newaction)
        data.runner = false
    end
end

do

    local enableaction  = tasks.enableaction
    local disableaction = tasks.disableaction

    function tasks.setaction(name,action,value)
        if value then
            enableaction(name,action)
        else
            disableaction(name,action)
        end
    end

end

function tasks.enablegroup(name,group)
    local data = validgroup(name,"enable group")
    if data then
        enablegroup(data.list,group)
        data.runner = false
    end
end

function tasks.disablegroup(name,group)
    local data = validgroup(name,"disable group")
    if data then
        disablegroup(data.list,group)
        data.runner = false
    end
end

function tasks.appendaction(name,group,action,where,kind)
    local data = validgroup(name,"append action")
    if data then
        appendaction(data.list,group,action,where,kind)
        data.runner = false
    end
end

function tasks.prependaction(name,group,action,where,kind)
    local data = validgroup(name,"prepend action")
    if data then
        prependaction(data.list,group,action,where,kind)
        data.runner = false
    end
end

function tasks.removeaction(name,group,action)
    local data = validgroup(name,"remove action")
    if data then
        removeaction(data.list,group,action)
        data.runner = false
    end
end

function tasks.showactions(name,group,action,where,kind)
    local data = valid(name)
    if data then
        report_tasks("task %a, list:\n%s",name,nodeprocessor(data.list))
    end
end

-- Optimizing for the number of arguments makes sense, but getting rid of
-- the nested call (no problem but then we also need to register the
-- callback with this mechanism so that it gets updated) does not save
-- much time (24K calls on mk.tex).

local created, total = 0, 0

statistics.register("node list callback tasks", function()
    if total > 0 then
        return format("%s unique task lists, %s instances (re)created, %s calls",table.count(tasksdata),created,total)
    else
        return nil
    end
end)

function tasks.actions(name) -- we optimize for the number or arguments (no ...)
    local data = tasksdata[name]
    if data then
        local n = data.arguments or 0
        if n == 0 then
            return function(head)
                total = total + 1 -- will go away
                local runner = data.runner
                if not runner then
                    created = created + 1
                    if trace_tasks then
                        report_tasks("creating runner %a",name)
                    end
                    runner = compile(data.list,data.processor,0)
                    data.runner = runner
                end
                return runner(head)
            end
        elseif n == 1 then
            return function(head,one)
                total = total + 1 -- will go away
                local runner = data.runner
                if not runner then
                    created = created + 1
                    if trace_tasks then
                        report_tasks("creating runner %a with %s extra arguments",name,1)
                    end
                    runner = compile(data.list,data.processor,1)
                    data.runner = runner
                end
                return runner(head,one)
            end
        elseif n == 2 then
            return function(head,one,two)
                total = total + 1 -- will go away
                local runner = data.runner
                if not runner then
                    created = created + 1
                    if trace_tasks then
                        report_tasks("creating runner %a with %s extra arguments",name,2)
                    end
                    runner = compile(data.list,data.processor,2)
                    data.runner = runner
                end
                return runner(head,one,two)
            end
        elseif n == 3 then
            return function(head,one,two,three)
                total = total + 1 -- will go away
                local runner = data.runner
                if not runner then
                    created = created + 1
                    if trace_tasks then
                        report_tasks("creating runner %a with %s extra arguments",name,3)
                    end
                    runner = compile(data.list,data.processor,3)
                    data.runner = runner
                end
                return runner(head,one,two,three)
            end
        elseif n == 4 then
            return function(head,one,two,three,four)
                total = total + 1 -- will go away
                local runner = data.runner
                if not runner then
                    created = created + 1
                    if trace_tasks then
                        report_tasks("creating runner %a with %s extra arguments",name,4)
                    end
                    runner = compile(data.list,data.processor,4)
                    data.runner = runner
                end
                return runner(head,one,two,three,four)
            end
        elseif n == 5 then
            return function(head,one,two,three,four,five)
                total = total + 1 -- will go away
                local runner = data.runner
                if not runner then
                    created = created + 1
                    if trace_tasks then
                        report_tasks("creating runner %a with %s extra arguments",name,5)
                    end
                    runner = compile(data.list,data.processor,5)
                    data.runner = runner
                end
                return runner(head,one,two,three,four,five)
            end
        else
            return function(head,...)
                total = total + 1 -- will go away
                local runner = data.runner
                if not runner then
                    created = created + 1
                    if trace_tasks then
                        report_tasks("creating runner %a with %s extra arguments",name,n)
                    end
                    runner = compile(data.list,data.processor,"n")
                    data.runner = runner
                end
                return runner(head,...)
            end
        end
    else
        return nil
    end
end

function tasks.table(name) --maybe move this to task-deb.lua
    local tsk = tasksdata[name]
    local lst = tsk and tsk.list
    local HL, NC, NR, bold, type = context.HL, context.NC, context.NR, context.bold, context.type
    if lst then
        local list, order = lst.list, lst.order
        if list and order then
            context.starttabulate { "|l|l|" }
            NC() bold("category") NC() bold("function") NC() NR()
            for i=1,#order do
                HL()
                local o = order[i]
                local l = list[o]
                if #l == 0 then
                    NC() type(o) NC() context("unset") NC() NR()
                else
                    local done = false
                    for k, v in table.sortedhash(l) do
                        NC() if not done then type(o) done = true end NC() type(v) NC() NR()
                    end
                end
            end
            context.stoptabulate()
        end
    end
end

-- this will move

tasks.new {
    name      = "processors",
    arguments = 5, -- often only the first is used, and the last three are only passed in hpack filter
--  arguments = 2,
    processor = nodeprocessor,
    sequence  = {
        "before",      -- for users
        "normalizers",
        "characters",
        "words",
        "fonts",
        "lists",
        "after",       -- for users
    }
}

tasks.new {
    name      = "finalizers",
    arguments = 1,
    processor = nodeprocessor,
    sequence  = {
        "before",      -- for users
        "normalizers",
--      "characters",
--      "finishers",
        "fonts",
        "lists",
        "after",       -- for users
    }
}

tasks.new {
    name      = "shipouts",
    arguments = 0,
 -- nostate   = true, -- maybe but only for main ones so little gain
    processor = nodeprocessor,
    sequence  = {
        "before",      -- for users
        "normalizers",
        "finishers",
        "after",       -- for users
    }
}

tasks.new {
    name      = "mvlbuilders",
    arguments = 1,
    processor = nodeprocessor,
    sequence  = {
        "before",      -- for users
        "normalizers",
        "after",       -- for users
    }
}

tasks.new {
    name      = "vboxbuilders",
    arguments = 5,
    processor = nodeprocessor,
    sequence  = {
        "before",      -- for users
        "normalizers",
        "after",       -- for users
    }
}

-- tasks.new {
--     name      = "parbuilders",
--     arguments = 1,
--     processor = nodeprocessor,
--     sequence  = {
--         "before",      -- for users
--         "lists",
--         "after",       -- for users
--     }
-- }

-- tasks.new {
--     name      = "pagebuilders",
--     arguments = 5,
--     processor = nodeprocessor,
--     sequence  = {
--         "before",      -- for users
--         "lists",
--         "after",       -- for users
--     }
-- }

tasks.new {
    name      = "contributers",
    arguments = 2, -- [head] where parent
    processor = nodeprocessor,
    sequence  = {
        "before",      -- for users
        "normalizers",
        "after",       -- for users
    }
}

