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

-- this module is being reconstructed

local next, type, format = next, type, string.format
local texsprint = tex.sprint

local ctxcatcodes = tex.ctxcatcodes

local glyph   = node.id('glyph')
local glue    = node.id('glue')
local rule    = node.id('rule')
local whatsit = node.id('whatsit')
local hlist   = node.id('hlist')
local vlist   = node.id('vlist')

local has_attribute = node.has_attribute
local copy_node     = node.copy

local starttiming, stoptiming = statistics.starttiming, statistics.stoptiming

states   = states or { }
shipouts = shipouts or { }

local numbers    = attributes.numbers
local trigger    = attributes.private('trigger')
local triggering = false

-- these two will be like trackers

function states.enabletriggering()
    triggering = true
end
function states.disabletriggering()
    triggering = false
end

-- the following code is no longer needed due to the new backend
-- but we keep it around for a while as an example
--
-- states.collected = states.collected or { }
--
-- storage.register("states/collected", states.collected, "states.collected")
--
-- local collected = states.collected
--
-- function states.collect(str)
--     collected[#collected+1] = str
-- end
--
-- function states.flush()
--     if #collected > 0 then
--         for i=1,#collected do
--             texsprint(ctxcatcodes,collected[i]) -- we're in context mode anyway
--         end
--         collected = { }
--         states.collected = collected
--     end
-- end
--
-- function states.check()
--     texio.write_nl(concat(collected,"\n"))
-- end

-- we used to do the main processor loop here and call processor for each node
-- but eventually this was too much a slow down (1 sec on 23 for 120 pages mk)
-- so that we moved looping to the processor itself; this may lead to a bit of
-- duplicate code once that we have more state handlers

local function process_attribute(head,plugin) -- head,attribute,enabled,initializer,resolver,processor,finalizer
    local namespace = plugin.namespace
    if namespace.enabled then
        starttiming(attributes)
        local done, used, ok = false, nil, false
        local attribute = namespace.attribute or numbers[plugin.name] -- todo: plugin.attribute
        local processor = plugin.processor
        if processor then
            local initializer = plugin.initializer
            local resolver    = plugin.resolver
            local inheritance = (resolver and resolver()) or nil -- -0x7FFFFFFF -- we can best use nil and skip !
            if initializer then
                initializer(namespace,attribute,head)
            end
            head, ok = processor(namespace,attribute,head,inheritance)
            if ok then
                local finalizer = plugin.finalizer
                if finalizer then
                    head, ok, used = finalizer(namespace,attribute,head)
                    if used then
                        local flusher = plugin.flusher
                        if flusher then
                            head = flusher(namespace,attribute,head,used)
                        end
                    end
                end
                done = true
            end
        end
        stoptiming(attributes)
        return head, done
    else
        return head, false
    end
end

nodes.process_attribute = process_attribute

function nodes.install_attribute_handler(plugin) -- we need to avoid this nested function
    return function(head)
        return process_attribute(head,plugin)
    end
end

--~ experiment (maybe local to function makes more sense)
--~
--~ plugindata = { }
--~
--~ local template = [[
--~ local plugin = plugindata["%s"]
--~ local starttiming, stoptiming = statistics.starttiming, statistics.stoptiming
--~ local namespace = plugin.namespace
--~ local attribute = namespace.attribute
--~ local processor = plugin.processor
--~ local initializer = plugin.initializer
--~ local resolver = plugin.resolver
--~ local finalizer = plugin.finalizer
--~ local flusher = plugin.flusher
--~ return function (head)
--~     if namespace.enabled then
--~         starttiming(attributes)
--~         local done, used, ok = false, nil, false
--~         if procesxsor then
--~             local inheritance = (resolver and resolver()) or nil -- -0x7FFFFFFF -- we can best use nil and skip !
--~             if initializer then
--~                 initializer(namespace,attribute,head)
--~             end
--~             head, ok = processor(namespace,attribute,head,inheritance)
--~             if ok then
--~                 if finalizer then
--~                     head, ok, used = finalizer(namespace,attribute,head)
--~                     if used and flusher then
--~                         head = flusher(namespace,attribute,head,used)
--~                     end
--~                 end
--~                 done = true
--~             end
--~         end
--~         stoptiming(attributes)
--~         return head, done
--~     else
--~         return head, false
--~     end
--~ end
--~ ]]
--~
--~ function nodes.install_attribute_handler(plugin) -- we need to avoid this nested function
--~     plugindata[plugin.name] = plugin
--~     local str = format(template,plugin.name)
--~     return loadstring(str)()
--~ end

-- the injectors

local insert_node_before = node.insert_before
local insert_node_after  = node.insert_after

local nsdata, nsdone, nsforced, nsselector, nstrigger
local current, current_selector, done = 0, 0, false -- nb, stack has a local current !

function states.initialize(namespace,attribute,head)
    nsdata, nsnone = namespace.data, namespace.none
    nsforced, nsselector, nslistwise = namespace.forced, namespace.selector, namespace.listwise
    nstrigger = triggering and namespace.triggering and trigger
    current, current_selector, done = 0, 0, false -- todo: done cleanup
end

function states.finalize(namespace,attribute,head) -- is this one ok?
    if current > 0 and nsnone then
        local id = head.id
        if id == hlist or id == vlist then
            local list = head.list
            if list then
                head.list = insert_node_before(list,list,copy_node(nsnone))
            end
        else
            head = insert_node_before(head,head,copy_node(nsnone))
        end
        return head, true, true
    end
    return head, false, false
end

local function process(namespace,attribute,head,inheritance,default) -- one attribute
    local stack, done = head, false
    while stack do
        local id = stack.id
        -- we need to deal with literals too (reset as well as oval)
        -- if id == glyph or (id == whatsit and stack.subtype == 8) or (id == rule and stack.width ~= 0) or (id == glue and stack.leader) then -- or disc
        if id == glyph or (id == rule and stack.width ~= 0) or (id == glue and stack.leader) then -- or disc
            local c = has_attribute(stack,attribute)
            if c then
                if default and c == inheritance then
                    if current ~= default then
                        head = insert_node_before(head,stack,copy_node(nsdata[default]))
                        current, done = default, true
                    end
                elseif current ~= c then
                    head = insert_node_before(head,stack,copy_node(nsdata[c]))
                    current, done = c, true
                end
                -- here ? compare selective
                if id == glue then --leader
                    -- same as *list
                    local content = stack.leader
                    if content then
                        local savedcurrent = current
                        local ci = content.id
                        if ci == hlist or ci == vlist then
                            -- else we reset inside a box unneeded, okay, the downside is
                            -- that we trigger color in each repeated box, so there is room
                            -- for improvement here
                            current = 0
                        end
                        local ok = false
                        if nstrigger and has_attribute(stack,nstrigger) then
                            local outer = has_attribute(stack,attribute)
                            if outer ~= inheritance then
                                stack.leader, ok = process(namespace,attribute,content,inheritance,outer)
                            else
                                stack.leader, ok = process(namespace,attribute,content,inheritance,default)
                            end
                        else
                            stack.leader, ok = process(namespace,attribute,content,inheritance,default)
                        end
                        current = savedcurrent
                        done = done or ok
                    end
                end
            elseif default and inheritance then
                if current ~= default then
                    head = insert_node_before(head,stack,copy_node(nsdata[default]))
                    current, done = default, true
                end
            elseif current > 0 then
                head = insert_node_before(head,stack,copy_node(nsnone))
                current, done = 0, true
            end
        elseif id == hlist or id == vlist then
            local content = stack.list
            if content then
                local ok = false
                if nstrigger and has_attribute(stack,nstrigger) then
                    local outer = has_attribute(stack,attribute)
                    if outer ~= inheritance then
                        stack.list, ok = process(namespace,attribute,content,inheritance,outer)
                    else
                        stack.list, ok = process(namespace,attribute,content,inheritance,default)
                    end
                else
                    stack.list, ok = process(namespace,attribute,content,inheritance,default)
                end
                done = done or ok
            end
        end
        stack = stack.next
    end
    return head, done
end

states.process = process

-- we can force a selector, e.g. document wide color spaces, saves a little
-- watch out, we need to check both the selector state (like colorspace) and
-- the main state (like color), otherwise we get into troubles when a selector
-- state changes while the main state stays the same (like two glyphs following
-- each other with the same color but different color spaces e.g. \showcolor)

local function selective(namespace,attribute,head,inheritance,default) -- two attributes
    local stack, done = head, false
    while stack do
        local id = stack.id
        -- we need to deal with literals too (reset as well as oval)
        -- if id == glyph or (id == whatsit and stack.subtype == 8) or (id == rule and stack.width ~= 0) or (id == glue and stack.leader) then -- or disc
        if id == glyph or (id == rule and stack.width ~= 0) or (id == glue and stack.leader) then -- or disc
            local c = has_attribute(stack,attribute)
            if c then
                if default and c == inheritance then
                    if current ~= default then
                        local data = nsdata[default]
                        head = insert_node_before(head,stack,copy_node(data[nsforced or has_attribute(stack,nsselector) or nsselector]))
                        current, done = default, true
                    end
                else
                    local s = has_attribute(stack,nsselector)
                    if current ~= c or current_selector ~= s then
                        local data = nsdata[c]
                        head = insert_node_before(head,stack,copy_node(data[nsforced or has_attribute(stack,nsselector) or nsselector]))
                        current, current_selector, done = c, s, true
                    end
                end
            elseif default and inheritance then
                if current ~= default then
                    local data = nsdata[default]
                    head = insert_node_before(head,stack,copy_node(data[nsforced or has_attribute(stack,nsselector) or nsselector]))
                    current, done = default, true
                end
            elseif current > 0 then
                head = insert_node_before(head,stack,copy_node(nsnone))
                current, current_selector, done = 0, 0, true
            end
            if id == glue then -- leader
                -- same as *list
                local content = stack.leader
                if content then
                    local savedcurrent = current
                    local ci = content.id
                    if ci == hlist or ci == vlist then
                        -- else we reset inside a box unneeded, okay, the downside is
                        -- that we trigger color in each repeated box, so there is room
                        -- for improvement here
                        current = 0
                    end
                    local ok = false
                    if nstrigger and has_attribute(stack,nstrigger) then
                        local outer = has_attribute(stack,attribute)
                        if outer ~= inheritance then
                            stack.leader, ok = selective(namespace,attribute,content,inheritance,outer)
                        else
                            stack.leader, ok = selective(namespace,attribute,content,inheritance,default)
                        end
                    else
                        stack.leader, ok = selective(namespace,attribute,content,inheritance,default)
                    end
                    current = savedcurrent
                    done = done or ok
                end
            end
        elseif id == hlist or id == vlist then
            local content = stack.list
            if content then
                local ok = false
                if nstrigger and has_attribute(stack,nstrigger) then
                    local outer = has_attribute(stack,attribute)
                    if outer ~= inheritance then
                        stack.list, ok = selective(namespace,attribute,content,inheritance,outer)
                    else
                        stack.list, ok = selective(namespace,attribute,content,inheritance,default)
                    end
                else
                    stack.list, ok = selective(namespace,attribute,content,inheritance,default)
                end
                done = done or ok
            end
        end
        stack = stack.next
    end
    return head, done
end

states.selective = selective

-- Ideally the next one should be merged with the previous but keeping it separate is
-- safer. We deal with two situations: efficient boxwise (layoutareas) and mixed layers
-- (as used in the stepper). In the stepper we cannot use the box branch as it involves
-- paragraph lines and then getsmixed up. A messy business (esp since we want to be
-- efficient).

local function stacked(namespace,attribute,head,default) -- no triggering, no inheritance, but list-wise
    local stack, done = head, false
    local current, depth = default or 0, 0
    while stack do
        local id = stack.id
        if id == glyph or (id == rule and stack.width ~= 0) or (id == glue and stack.leader) then -- or disc
            local c = has_attribute(stack,attribute)
            if c then
                if current ~= c then
                    head = insert_node_before(head,stack,copy_node(nsdata[c]))
                    depth = depth + 1
                    current, done = c, true
                end
                if id == glue then
                    local content = stack.leader
                    if content then -- unchecked
                        local ok = false
                        stack.leader, ok = stacked(namespace,attribute,content,current)
                        done = done or ok
                    end
                end
            elseif default then
                --
            elseif current > 0 then
                head = insert_node_before(head,stack,copy_node(nsnone))
                depth = depth - 1
                current, done = 0, true
            end
        elseif id == hlist or id == vlist then
            local content = stack.list
            if content then
             -- the problem is that broken lines gets the attribute which can be a later one
                if nslistwise then
                    local c = has_attribute(stack,attribute)
                    if c and current ~= c and nslistwise[c] then -- viewerlayer
                        local p = current
                        current, done = c, true
                        head = insert_node_before(head,stack,copy_node(nsdata[c]))
                        stack.list = stacked(namespace,attribute,content,current)
                        head, stack = insert_node_after(head,stack,copy_node(nsnone))
                        current = p
                    else
                        local ok = false
                        stack.list, ok = stacked(namespace,attribute,content,current)
                        done = done or ok
                    end
                else
                    local ok = false
                    stack.list, ok = stacked(namespace,attribute,content,current)
                    done = done or ok
                end
            end
        end
        stack = stack.next
    end
    while depth > 0 do
        head = insert_node_after(head,stack,copy_node(nsnone))
        depth = depth -1
    end
    return head, done
end

states.stacked = stacked

-- -- --

statistics.register("attribute processing time", function()
    if statistics.elapsedindeed(attributes) then
        return format("%s seconds (front- and backend)",statistics.elapsedtime(attributes))
    end
end)
