if not modules then modules = { } end modules ['node-res'] = {
    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"
}

local type, next, rawset = type, next, rawset
local gmatch, format = string.gmatch, string.format
local round = math.round
local concat, sortedhash, setmetatableindex = table.concat, table.sortedhash, table.setmetatableindex

local nodes, node = nodes, node

local report_nodes   = logs.reporter("nodes","housekeeping")

nodes.pool           = nodes.pool or { }
local nodepool       = nodes.pool

local gluecodes      = nodes.gluecodes
local kerncodes      = nodes.kerncodes
local rulecodes      = nodes.rulecodes
local nodecodes      = nodes.nodecodes
local boundarycodes  = nodes.boundarycodes
local usercodes      = nodes.usercodes

local nodeproperties = nodes.properties.data

local glyph_code     <const> = nodecodes.glyph
local rule_code      <const> = nodecodes.rule
local kern_code      <const> = nodecodes.kern
local glue_code      <const> = nodecodes.glue
local gluespec_code  <const> = nodecodes.gluespec

local currentfont    = font.current
local texgetcount    = tex.getcount

local allocate       = utilities.storage.allocate

local reserved       = { }
local nofreserved    = 0

-- nuts overload

local nuts         = nodes.nuts
local nutpool      = { }
nuts.pool          = nutpool

local tonut        = nuts.tonut
local tonode       = nuts.tonode
local isdirect     = nuts.isdirect

local getbox       = nuts.getbox
local getid        = nuts.getid
local getlist      = nuts.getlist
local getglue      = nuts.getglue

local setfield     = nuts.setfield
local setchar      = nuts.setchar
local setlist      = nuts.setlist
local setwhd       = nuts.setwhd
local setglue      = nuts.setglue
local setdisc      = nuts.setdisc
local setfont      = nuts.setfont
local setkern      = nuts.setkern
local setpenalty   = nuts.setpenalty
local setdir       = nuts.setdir
local setdirection = nuts.setdirection
local setshift     = nuts.setshift
local setwidth     = nuts.setwidth
local setsubtype   = nuts.setsubtype
local setleader    = nuts.setleader
local setclass     = nuts.setclass
local setdata      = nuts.setdata
local setoffsets   = nuts.setoffsets
local setvalue     = nuts.setvalue

local setruledimensions = nuts.setruledimensions

local copy_nut     = nuts.copyonly
local new_nut      = nuts.new
local flush_nut    = nuts.flush

-- at some point we could have a dual set (the overhead of tonut is not much larger than
-- metatable associations at the lua/c end esp if we also take assignments into account

-- setmetatableindex(nodepool,function(t,k,v)
--  -- report_nodes("defining nodepool[%s] instance",k)
--     local f = nutpool[k]
--     local v = function(...)
--         return tonode(f(...))
--     end
--     t[k] = v
--     return v
-- end)
--
-- -- we delay one step because that permits us a forward reference
-- -- e.g. in pdfsetmatrix

setmetatableindex(nodepool,function(t,k,v)
 -- report_nodes("defining nodepool[%s] instance",k)
    local v = function(...)
        local f = nutpool[k]
        local v = function(...)
            return tonode(f(...))
        end
        t[k] = v
        return v(...)
    end
    t[k] = v
    return v
end)

local function register_nut(n)
    nofreserved = nofreserved + 1
    reserved[nofreserved] = n
    return n
end

local function register_node(n)
    nofreserved = nofreserved + 1
    if type(n) == "number" then -- isnut(n)
        reserved[nofreserved] = n
    else
        reserved[nofreserved] = tonut(n)
    end
    return n
end

nodepool.register = register_node
nutpool.register  = register_node -- could be register_nut

-- so far

local disc              = register_nut(new_nut(nodecodes.disc))
local kern              = register_nut(new_nut(kern_code,kerncodes.userkern))
local fontkern          = register_nut(new_nut(kern_code,kerncodes.fontkern))
local italiccorrection  = register_nut(new_nut(kern_code,kerncodes.italiccorrection))
local spacefontkern     = register_nut(new_nut(kern_code,kerncodes.spacefontkern))
local penalty           = register_nut(new_nut(nodecodes.penalty))
local glue              = register_nut(new_nut(glue_code))
local gluespec          = register_nut(new_nut(gluespec_code))
local glyph             = register_nut(new_nut(glyph_code,0))

local textdir           = register_nut(new_nut(nodecodes.dir))

local left_margin_kern  = register_nut(new_nut(kern_code,kerncodes.leftmargincode))
local right_margin_kern = register_nut(new_nut(kern_code,kerncodes.rightmargincode))

local lineskip          = register_nut(new_nut(glue_code,gluecodes.lineskip))
local baselineskip      = register_nut(new_nut(glue_code,gluecodes.baselineskip))
local leftskip          = register_nut(new_nut(glue_code,gluecodes.leftskip))
local rightskip         = register_nut(new_nut(glue_code,gluecodes.rightskip))
local lefthangskip      = register_nut(new_nut(glue_code,gluecodes.lefthangskip))
local righthangskip     = register_nut(new_nut(glue_code,gluecodes.righthangskip))
local indentskip        = register_nut(new_nut(glue_code,gluecodes.indentskip))
local correctionskip    = register_nut(new_nut(glue_code,gluecodes.correctionskip))

local temp              = register_nut(new_nut(nodecodes.temp,0))

local noad              = register_nut(new_nut(nodecodes.noad))
local delimiter         = register_nut(new_nut(nodecodes.delimiter))
local fence             = register_nut(new_nut(nodecodes.fence))
local submlist          = register_nut(new_nut(nodecodes.submlist))
local accent            = register_nut(new_nut(nodecodes.accent))
local radical           = register_nut(new_nut(nodecodes.radical))
local fraction          = register_nut(new_nut(nodecodes.fraction))
local subbox            = register_nut(new_nut(nodecodes.subbox))
local mathchar          = register_nut(new_nut(nodecodes.mathchar))
local mathtextchar      = register_nut(new_nut(nodecodes.mathtextchar))
local choice            = register_nut(new_nut(nodecodes.choice))

local boundary          = register_nut(new_nut(nodecodes.boundary,boundarycodes.user))
local wordboundary      = register_nut(new_nut(nodecodes.boundary,boundarycodes.word))
local luaboundary       = register_nut(new_nut(nodecodes.boundary,boundarycodes.lua))

local cleader           = register_nut(copy_nut(glue)) setsubtype(cleader,gluecodes.cleaders) setglue(cleader,0,65536,0,2,0)

-- the dir field needs to be set otherwise crash:

local lefttoright_code  <const> = tex.directioncodes.lefttoright

local rule              = register_nut(new_nut(rule_code))                   -- setdirection(rule, lefttoright_code)
local emptyrule         = register_nut(new_nut(rule_code,rulecodes.empty))   -- setdirection(rule, lefttoright_code)
local strutrule         = register_nut(new_nut(rule_code,rulecodes.strut))   -- setdirection(rule, lefttoright_code)
local userrule          = register_nut(new_nut(rule_code,rulecodes.user))    -- setdirection(rule, lefttoright_code)
local outlinerule       = register_nut(new_nut(rule_code,rulecodes.outline)) -- setdirection(rule, lefttoright_code)
local imagerule         = register_nut(new_nut(rule_code,rulecodes.image))   -- setdirection(rule, lefttoright_code)
local boxrule           = register_nut(new_nut(rule_code,rulecodes.box))     -- setdirection(rule, lefttoright_code)
local virtualrule       = register_nut(new_nut(rule_code,rulecodes.virtual)) -- setdirection(rule, lefttoright_code)
local spacingrule       = register_nut(new_nut(rule_code,rulecodes.spacing)) -- setdirection(rule, lefttoright_code)
local hlist             = register_nut(new_nut(nodecodes.hlist))                setdirection(hlist,lefttoright_code)
local vlist             = register_nut(new_nut(nodecodes.vlist))                setdirection(vlist,lefttoright_code)

function nutpool.glyph(fnt,chr)
    local n = copy_nut(glyph)
    if fnt then
        setfont(n,fnt == true and currentfont() or fnt,chr)
    elseif chr then
        setchar(n,chr)
    end
    return n
end

function nutpool.penalty(p)
    local n = copy_nut(penalty)
    if p and p ~= 0 then
        setpenalty(n,p)
    end
    return n
end

function nutpool.kern(k)
    local n = copy_nut(kern)
    if k and k ~= 0 then
        setkern(n,k)
    end
    return n
end

function nutpool.boundary(v)
    local n = copy_nut(boundary)
    if v and v ~= 0 then
        setvalue(n,v)
    end
    return n
end

function nutpool.wordboundary(v)
    local n = copy_nut(wordboundary)
    if v and v ~= 0 then
        setvalue(n,v)
    end
    return n
end

function nutpool.luaboundary(v,e)
    local n = copy_nut(luaboundary)
    if v and v ~= 0 then
        setvalue(n,v,e or 0)
    end
    return n
end

function nutpool.fontkern(k)
    local n = copy_nut(fontkern)
    if k and k ~= 0 then
        setkern(n,k)
    end
    return n
end

function nutpool.italiccorrection(k)
    local n = copy_nut(italiccorrection)
    if k and k ~= 0 then
        setkern(n,k)
    end
    return n
end

nutpool.italickern = nutpool.italiccorrection -- compatibility

function nutpool.spacefontkern(k)
    local n = copy_nut(spacefontkern)
    if k and k ~= 0 then
        setkern(n,k)
    end
    return n
end

function nutpool.gluespec(width,stretch,shrink,stretch_order,shrink_order)
    local n = copy_nut(gluespec)
    if width or stretch or shrink or stretch_order or shrink_order then
        setglue(n,width,stretch,shrink,stretch_order,shrink_order)
    end
    return n
end

local function someskip(skip,width,stretch,shrink,stretch_order,shrink_order)
    -- maybe setglue
    local n = copy_nut(skip)
    if width or stretch or shrink or stretch_order or shrink_order then
        setglue(n,width,stretch,shrink,stretch_order,shrink_order)
    end
    return n
end

function nutpool.stretch(a,b)
    -- width stretch shrink stretch_order shrink_order
    local n = copy_nut(glue)
    if not b then
        a, b = 1, a or 1
    end
    setglue(n,0,a,0,b,0)
    return n
end

function nutpool.shrink(a,b)
    local n = copy_nut(glue)
    if not b then
        a, b = 1, a or 1
    end
    setglue(n,0,0,a,0,0,b)
    return n
end

function nutpool.glue(width,stretch,shrink,stretch_order,shrink_order)
    return someskip(glue,width,stretch,shrink,stretch_order,shrink_order)
end

function nutpool.negatedglue(glue)
    local n = copy_nut(glue)
    local width, stretch, shrink = getglue(n)
    setglue(n,-width,-stretch,-shrink)
    return n
end

function nutpool.leftskip(width,stretch,shrink,stretch_order,shrink_order)
    return someskip(leftskip,width,stretch,shrink,stretch_order,shrink_order)
end

function nutpool.rightskip(width,stretch,shrink,stretch_order,shrink_order)
    return someskip(rightskip,width,stretch,shrink,stretch_order,shrink_order)
end

function nutpool.lefthangskip(width,stretch,shrink,stretch_order,shrink_order)
    return someskip(lefthangskip,width,stretch,shrink,stretch_order,shrink_order)
end

function nutpool.righthangskip(width,stretch,shrink,stretch_order,shrink_order)
    return someskip(righthangskip,width,stretch,shrink,stretch_order,shrink_order)
end

function nutpool.indentskip(width,stretch,shrink,stretch_order,shrink_order)
    return someskip(indentskip,width,stretch,shrink,stretch_order,shrink_order)
end

function nutpool.lineskip(width,stretch,shrink,stretch_order,shrink_order)
    return someskip(lineskip,width,stretch,shrink,stretch_order,shrink_order)
end

function nutpool.baselineskip(width,stretch,shrink)
    return someskip(baselineskip,width,stretch,shrink)
end

function nutpool.disc(pre,post,replace)
    local d = copy_nut(disc)
    if pre or post or replace then
        setdisc(d,pre,post,replace)
    end
    return d
end

function nutpool.direction(dir,swap)
    local t = copy_nut(textdir)
    if not dir then
        -- just a l2r start node
    elseif swap then
        setdirection(t,dir,true)
    else
        setdirection(t,dir,false)
    end
    return t
end

function nutpool.rule(width,height,depth) -- w/h/d == nil will let them adapt
    local n = copy_nut(rule)
    if width or height or depth then
        setwhd(n,width,height,depth)
    end
    return n
end

function nutpool.emptyrule(width,height,depth) -- w/h/d == nil will let them adapt
    local n = copy_nut(emptyrule)
    if width or height or depth then
        setwhd(n,width,height,depth)
    end
    return n
end

-- data left right

function nutpool.strutrule(width,height,depth) -- w/h/d == nil will let them adapt
    local n = copy_nut(strutrule)
    if width or height or depth then
        setwhd(n,width,height,depth)
    end
    return n
end

function nutpool.userrule(width,height,depth) -- w/h/d == nil will let them adapt
    local n = copy_nut(userrule)
    if width or height or depth then
        setwhd(n,width,height,depth)
    end
    return n
end

function nutpool.outlinerule(width,height,depth,line) -- w/h/d == nil will let them adapt
    local n = copy_nut(outlinerule)
    if width or height or depth then
        setwhd(n,width,height,depth)
    end
    if line then
        setdata(n,round(line)) -- has to be an integer
    end
    return n
end

function nutpool.imagerule(width,height,depth) -- w/h/d == nil will let them adapt
    local n = copy_nut(imagerule)
    if width or height or depth then
        setwhd(n,width,height,depth)
    end
    return n
end

function nutpool.boxrule(width,height,depth) -- w/h/d == nil will let them adapt
    local n = copy_nut(boxrule)
    if width or height or depth then
        setwhd(n,width,height,depth)
    end
    return n
end

function nutpool.virtualrule(width,height,depth,data)
    local n = copy_nut(virtualrule)
    if width or height or depth or data then
        setruledimensions(n,width,height,depth,data)
    end
    return n
end

function nutpool.spacingrule(width,height,depth) -- w/h/d == nil will let them adapt
    local n = copy_nut(spacingrule)
    if width or height or depth then
        setwhd(n,width,height,depth)
    end
    return n
end

local function new_leader(width,list)
    local n = copy_nut(cleader)
    if width then
        setwidth(n,width)
    end
    if list then
        setleader(n,list)
    end
    return n
end

nutpool.leader = new_leader

function nodepool.leader(width,list)
    return tonode(new_leader(width,list and tonut(list)))
end

function nutpool.leftmarginkern(glyph,width)
    local n = copy_nut(left_margin_kern)
    if not glyph then
        report_nodes("invalid pointer to left margin glyph node")
    elseif getid(glyph) ~= glyph_code then
        report_nodes("invalid node type %a for %s margin glyph node",nodecodes[glyph],"left")
    else
        setfield(n,"glyph",glyph)
    end
    if width and width ~= 0 then
        setwidth(n,width)
    end
    return n
end

function nutpool.rightmarginkern(glyph,width)
    local n = copy_nut(right_margin_kern)
    if not glyph then
        report_nodes("invalid pointer to right margin glyph node")
    elseif getid(glyph) ~= glyph_code then
        report_nodes("invalid node type %a for %s margin glyph node",nodecodes[p],"right")
    else
        setfield(n,"glyph",glyph)
    end
    if width and width ~= 0 then
        setwidth(n,width)
    end
    return n
end

function nutpool.temp()
    return copy_nut(temp)
end

function nutpool.noad(class)
    local n = copy_nut(noad)
    if class then
        setsubtype(n,class)
        setclass(n,class,class,class)
    end
    return n
end

-- maybe also the rest wrt subtype and class

function nutpool.delimiter()    return copy_nut(delimiter)    end  nutpool.delim = nutpool.delimiter
function nutpool.fence()        return copy_nut(fence)        end
function nutpool.submlist()     return copy_nut(submlist)     end
function nutpool.fence()        return copy_nut(fence)        end
function nutpool.accent()       return copy_nut(accent)       end
function nutpool.radical()      return copy_nut(radical)      end
function nutpool.fraction()     return copy_nut(fraction)     end
function nutpool.subbox()       return copy_nut(subbox)       end
function nutpool.mathchar()     return copy_nut(mathchar)     end
function nutpool.mathtextchar() return copy_nut(mathtextchar) end
function nutpool.choice()       return copy_nut(choice)       end

local function new_hlist(list,width,height,depth,shift)
    local n = copy_nut(hlist)
    if list then
        setlist(n,list)
    end
    if width or height or depth then
        setwhd(n,width,height,depth)
    end
    if shift and shift ~= 0 then
        setshift(n,shift)
    end
    return n
end

local function new_vlist(list,width,height,depth,shift)
    local n = copy_nut(vlist)
    if list then
        setlist(n,list)
    end
    if width or height or depth then
        setwhd(n,width,height,depth)
    end
    if shift and shift ~= 0 then
        setshift(n,shift)
    end
    return n
end

nutpool.hlist = new_hlist
nutpool.vlist = new_vlist

function nodepool.hlist(list,width,height,depth,shift)
    return tonode(new_hlist(list and tonut(list),width,height,depth,shift))
end

function nodepool.vlist(list,width,height,depth,shift)
    return tonode(new_vlist(list and tonut(list),width,height,depth,shift))
end

function nutpool.usernode(id,data)
    local n = copy_nut(user_node)
    nodeproperties[n] = {
        id   = id,
        data = data,
    }
    return n
end

-- housekeeping

local trace = false

trackers.register("attributes.tracking", function(v) trace = v end)

statistics.register("attribute tracking", function()
    if trace then
        local t, n = { }, 0
        local track = tex.attributetracking
        local names = attributes.names
        for i=0,track() do
            local name = names[i]
            if name then
                local count = track(i)
                if count and count > 0 then
                    n = n + 1 ; t[n] = format("%s %s",name,count)
                end
            end
        end
        if n > 0 then
            return concat(t,", ")
        end
    end
end)

local function cleanup(nofboxes) -- todo
    -- this is bonus, not really needed
    local tracers = nodes.tracers
    if tracers and tracers.steppers then -- to be resolved
        tracers.steppers.reset() -- todo: make a registration subsystem
    end
    local nl = 0
    local nr = nofreserved
    for i=1,nofreserved do
        local ri = reserved[i]
        flush_nut(reserved[i])
    end
    if nofboxes then
        for i=0,nofboxes do
            local l = getbox(i)
            if l then
                flush_nut(l) -- also list ?
                nl = nl + 1
            end
        end
    end
    reserved    = { }
    nofreserved = 0
    return nr, nl, nofboxes -- can be nil
end

local usage = node.inuse
local stock = node.instock

nutpool .cleanup = cleanup
nodepool.cleanup = cleanup

nutpool .usage   = usage
nodepool.usage   = usage

nutpool .stock   = stock
nodepool.stock   = stock

-- end

statistics.register("cleaned up reserved nodes", function()
    return format("%s nodes, %s lists of %s", cleanup(texgetcount("c_syst_last_allocated_box")))
end) -- \topofboxstack

-- local function pusage()
--     local check = setmetatableindex(function(t,k)
--         local v = { good = 0, bad = 0 }
--         t[k] = v
--         return v
--     end)
--     for k, v in next, nodeproperties do
--         local ck = check[nodecodes[getid(k)]]
--         if isdirect(k) then
--             ck.good = ck.good + 1
--         else
--             ck.bad = ck.bad + 1
--         end
--     end
--     return check
-- end

statistics.register("node memory usage", function() -- comes after cleanup !
    local used = usage()
    local t, n, s = { }, 0, nil
    if next(used) then
        for k, v in sortedhash(used) do
            n = n + 1 ; t[n] = format("%s %s",v,k)
        end
    end
    local c = table.count(nodeproperties)
    if c > 0 then
        n = n + 1 ; t[n] = format("stale properties: %i",c)
    end
    if n > 0 then
        s = concat(t,", ")
    end
 -- local prop = pusage()
 -- if next(prop) then
 --     if s then
 --         s = s .. ", "
 --     else
 --         s = ""
 --     end
 --     n, t, s = { }, 0, s .. "properties: "
 --     for k, v in sortedhash(prop) do
 --         n = n + 1 ; t[n] = format("%s %i:%i",k,v.good,v.bad)
 --     end
 --     s = s .. concat(t,", ")
 -- end
    return s
end)

lua.registerinitexfinalizer(cleanup, "cleanup reserved nodes")

do

    local glyph      = glyph
    local traverseid = nuts.traverseid

    local traversers = setmetatableindex(function(t,k)
        local v = traverseid(type(k) == "number" and k or nodecodes[k],glyph)
        t[k] = v
        return v
    end)

    local treversers = setmetatableindex(function(t,k)
        local v = traverseid(type(k) == "number" and k or nodecodes[k],glyph,true)
        t[k] = v
        return v
    end)

    -- these are special:

    traversers.node     = nuts.traverse        (glyph)
    traversers.char     = nuts.traversechar    (glyph)
    traversers.glyph    = nuts.traverseglyph   (glyph)
    traversers.list     = nuts.traverselist    (glyph)
    traversers.content  = nuts.traversecontent (glyph)
    traversers.possible = nuts.traversepossible(glyph)
    traversers.leader   = nuts.traverseleader  (glyph)

    treversers.node     = nuts.traverse        (glyph,true)
    treversers.char     = nuts.traversechar    (glyph,true)
    treversers.glyph    = nuts.traverseglyph   (glyph,true)
    treversers.list     = nuts.traverselist    (glyph,true)
    treversers.content  = nuts.traversecontent (glyph,true)
    treversers.possible = nuts.traversepossible(glyph,true)
    treversers.leader   = nuts.traverseleader  (glyph,true)

    nuts.traversers = traversers
    nuts.treversers = treversers

end
