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

--[[ldx--
<p>This is rather experimental. We need more control and some of this
might become a runtime module instead.</p>
--ldx]]--

local utf = unicode.utf8
local format, match, concat, utfchar = string.format, string.match, table.concat, utf.char

local ctxcatcodes = tex.ctxcatcodes

fonts     = fonts     or { }
fonts.tfm = fonts.tfm or { }
fonts.ids = fonts.ids or { }

nodes                    = nodes                    or { }
nodes.tracers            = nodes.tracers            or { }
nodes.tracers.characters = nodes.tracers.characters or { }
nodes.tracers.steppers   = nodes.tracers.steppers   or { }

local glyph   = node.id('glyph')
local disc    = node.id('disc')
local glue    = node.id('glue')
local kern    = node.id('kern')
local whatsit = node.id('whatsit')

local copy_node_list  = node.copy_list
local hpack_node_list = node.hpack
local free_node_list  = node.flush_list
local first_character = node.first_character
local node_type       = node.type
local traverse_nodes  = node.traverse

local texsprint = tex.sprint
local fontdata  = fonts.ids

function nodes.tracers.characters.collect(head,list,tag,n)
    n = n or 0
    local ok, fn = false, nil
    while head do
        local id = head.id
        if id == glyph then
            local f = head.font
            if f ~= fn then
                ok, fn = false, f
            end
            local c = head.char
            local i = fontdata[f].indices[c] or 0
            if not ok then
                ok = true
                n = n + 1
                list[n] = list[n] or { }
                list[n][tag] = { }
            end
            local l = list[n][tag]
            l[#l+1] = { c, f, i }
        elseif id == disc then
            -- skip
        else
            ok = false
        end
        head = head.next
    end
end

function nodes.tracers.characters.equal(ta, tb)
    if #ta ~= #tb then
        return false
    else
        for i=1,#ta do
            local a, b = ta[i], tb[i]
            if a[1] ~= b[1] or a[2] ~= b[2] or a[3] ~= b[3] then
                return false
            end
        end
    end
    return true
end

function nodes.tracers.characters.string(t)
    local tt = { }
    for i=1,#t do
        tt[i] = utfchar(t[i][1])
    end
    return concat(tt,"")
end

function nodes.tracers.characters.unicodes(t,decimal)
    local tt = { }
    for i=1,#t do
        local n = t[i][1]
        if n == 0 then
            tt[i] = "-"
        elseif decimal then
            tt[i] = n
        else
            tt[i] = format("U+%04X",n)
        end
    end
    return concat(tt," ")
end

function nodes.tracers.characters.indices(t,decimal)
    local tt = { }
    for i=1,#t do
        local n = t[i][3]
        if n == 0 then
            tt[i] = "-"
        elseif decimal then
            tt[i] = n
        else
            tt[i] = format("U+%04X",n)
        end
    end
    return concat(tt," ")
end

function nodes.tracers.characters.start()
    local npc = nodes.process_characters
    local list = { }
    function nodes.process_characters(head)
        local n = #list
        nodes.tracers.characters.collect(head,list,'before',n)
        local h, d = npc(head)
        nodes.tracers.characters.collect(head,list,'after',n)
        if #list > n then
            list[#list+1] = { }
        end
        return h, d
    end
    function nodes.tracers.characters.stop()
        tracers.list['characters'] = list
        lmx.set('title', 'ConTeXt Character Processing Information')
        lmx.set('color-background-one', lmx.get('color-background-yellow'))
        lmx.set('color-background-two', lmx.get('color-background-purple'))
        lmx.show('context-characters.lmx')
        lmx.restore()
        nodes.process_characters = npc
        tasks.restart("processors", "characters")
    end
    tasks.restart("processors", "characters")
end

local stack = { }

function nodes.tracers.start(tag)
    stack[#stack+1] = tag
    local tracer = nodes.tracers[tag]
    if tracer and tracer.start then
        tracer.start()
    end
end
function nodes.tracers.stop()
    local tracer = stack[#stack]
    if tracer and tracer.stop then
        tracer.stop()
    end
    stack[#stack] = nil
end

-- experimental

local collection, collecting, messages = { }, false, { }

function nodes.tracers.steppers.start()
    collecting = true
end

function nodes.tracers.steppers.stop()
    collecting = false
end

function nodes.tracers.steppers.reset()
    for i=1,#collection do
        local c = collection[i]
        if c then
            free_node_list(c)
        end
    end
    collection, messages = { }, { }
end

function nodes.tracers.steppers.nofsteps()
    return tex.write(#collection)
end

function nodes.tracers.steppers.glyphs(n,i)
    local c = collection[i]
    if c then
        tex.box[n] = hpack_node_list(copy_node_list(c))
    end
end

function nodes.tracers.steppers.features()
--  local f = first_character(collection[1])
--  if f then -- something fishy with first_character
    local f = collection[1]
    while f do
        if f.id == glyph then
            local tfmdata, t = fontdata[f.font], { }
            for feature, value in table.sortedpairs(tfmdata.shared.features) do
                if feature == "number" or feature == "features" then
                    -- private
                elseif type(value) == "boolean" then
                    if value then
                        t[#t+1] = format("%s=yes",feature)
                    else
                        -- skip
                    end
                else
                    t[#t+1] = format("%s=%s",feature,value)
                end
            end
            if #t > 0 then
                texsprint(ctxcatcodes,concat(t,", "))
            else
                texsprint(ctxcatcodes,"no features")
            end
            return
        end
        f = f.next
    end
end

function nodes.tracers.fontchar(font,char)
    local n = nodes.glyph()
    n.font, n.char, n.subtype = font, char, 256
    node.write(n)
end

function nodes.tracers.steppers.codes(i,command)
    local c = collection[i]
    while c do
        local id = c.id
        if id == glyph then
            if command then
                texsprint(ctxcatcodes,format("%s{%s}{%s}",command,c.font,c.char))
            else
                texsprint(ctxcatcodes,format("[%s:U+%04X]",c.font,c.char))
            end
        elseif id == whatsit and (c.subtype == 6 or c.subtype == 7) then
            texsprint(ctxcatcodes,format("[%s]",c.dir))
        else
            texsprint(ctxcatcodes,format("[%s]",node_type(id)))
        end
        c = c.next
    end
end

function nodes.tracers.steppers.messages(i,command,split)
    local list = messages[i] -- or { "no messages" }
    if list then
        for i=1,#list do
            local l = list[i]
            if split then
                local a, b = match(l,"^(.-)%s*:%s*(.*)$")
                texsprint(ctxcatcodes,format("%s{%s}{%s}",command,a or l,b or ""))
            else
                texsprint(ctxcatcodes,format("%s{%s}",command,l))
            end
        end
    end
end

-- hooks into the node list processor (see otf)

function nodes.tracers.steppers.check(head)
    if collecting then
        nodes.tracers.steppers.reset()
        local n = copy_node_list(head)
        nodes.inject_kerns(n,nil,"trace",true)
        nodes.protect_glyphs(n) -- can be option
        collection[1] = n
    end
end

function nodes.tracers.steppers.register(head)
    if collecting then
        local nc = #collection+1
        if messages[nc] then
            local n = copy_node_list(head)
            nodes.inject_kerns(n,nil,"trace",true)
            nodes.protect_glyphs(n) -- can be option
            collection[nc] = n
        end
    end
end

function nodes.tracers.steppers.message(str,...)
    str = format(str,...)
    if collecting then
        local n = #collection + 1
        local m = messages[n]
        if not m then m = { } messages[n] = m end
        m[#m+1] = str
    end
    return str -- saves an intermediate var in the caller
end

-- this will be reorganized:

function nodes.show_list(head, message)
    if message then
        texio.write_nl(message)
    end
    for n in traverse(head) do
        texio.write_nl(tostring(n))
    end
end

function nodes.check_glyphs(head,message)
    local t = { }
    for g in traverse_id(glyph,head) do
        t[#t+1] = format("U+%04X:%s",g.char,g.subtype)
    end
    if #t > 0 then
        logs.report(message or "nodes","%s glyphs: %s",#t,concat(t," "))
    end
    return false
end

function nodes.tosequence(start,stop,compact)
    if start then
        local t = { }
        while start do
            if start.id == glyph then
                local c = start.char
                if compact then
                    if start.components then
                        t[#t+1] = nodes.tosequence(start.components,nil,compact)
                    else
                        t[#t+1] = format("%s",utfchar(c))
                    end
                else
                    t[#t+1] = format("U+%04X:%s",c,utfchar(c))
                end
            else
                if compact then
                    t[#t+1] = "[]"
                else
                    t[#t+1] = match(tostring(start),": (%S+)")
                end
            end
            if start == stop then
                break
            else
                start = start.next
            end
        end
        if compact then
            return concat(t)
        else
            return concat(t," ")
        end
    else
        return "<empty>"
    end
end

function nodes.report(t,done)
    if done then
        if status.output_active then
            texio.write(format("<++ %s>",count(t)))
        else
            texio.write(format("<+ %s>",count(t)))
        end
    else
        if status.output_active then
            texio.write(format("<-- %s>",count(t)))
        else
            texio.write(format("<- %s>",count(t)))
        end
    end
end

function nodes.pack_list(head)
    local t = { }
    for n in traverse(head) do
        t[#t+1] = tostring(n)
    end
    return t
end

function nodes.ids_to_string(head)
    local t, last_id, last_n = { }, nil, 0
    for n in traverse_nodes(head) do
        local id = n.id
        if not last_id then
            last_id, last_n = id, 1
        elseif last_id == id then
            last_n = last_n + 1
        else
            if last_n > 1 then
                t[#t+1] = format("[%s*%s]",last_n,node_type(last_id) or "?")
            else
                t[#t+1] = format("[%s]",node_type(last_id) or "?")
            end
            last_id, last_n = id, 1
        end
    end
    if not last_id then
        t[#t+1] = "no nodes"
    elseif last_n > 1 then
        t[#t+1] = format("[%s*%s]",last_n,node_type(last_id) or "?")
    else
        t[#t+1] = format("[%s]",node_type(last_id) or "?")
    end
    return concat(t," ")
end
