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

-- experimental -> will become builders

-- if there is demand for it, we can support multiple numbering streams
-- and use more than one attibute

local next, tonumber = next, tonumber

local trace_numbers      = false  trackers.register("lines.numbers",  function(v) trace_numbers = v end)

local report_lines       = logs.reporter("lines")

local attributes         = attributes
local nodes              = nodes
local context            = context

local implement          = interfaces.implement

nodes.lines              = nodes.lines or { }
local lines              = nodes.lines

lines.data               = lines.data or { } -- start step tag
local data               = lines.data
local last               = #data

lines.scratchbox         = lines.scratchbox or 0

storage.register("lines/data", data, "nodes.lines.data")

local variables          = interfaces.variables

local v_next             = variables.next
local v_page             = variables.page
local v_no               = variables.no

local nodecodes          = nodes.nodecodes
local skipcodes          = nodes.skipcodes
local whatcodes          = nodes.whatcodes

local hlist_code         = nodecodes.hlist
local vlist_code         = nodecodes.vlist
local whatsit_code       = nodecodes.whatsit
local glue_code          = nodecodes.glue
local glyph_code         = nodecodes.glyph
local leftskip_code      = skipcodes.leftskip
local textdir_code       = whatcodes.dir

local a_displaymath      = attributes.private('displaymath')
local a_linenumber       = attributes.private('linenumber')
local a_linereference    = attributes.private('linereference')
local a_verbatimline     = attributes.private('verbatimline')

local current_list       = { }
local cross_references   = { }
local chunksize          = 250 -- not used in boxed

local nuts               = nodes.nuts

local getid              = nuts.getid
local getsubtype         = nuts.getsubtype
local getnext            = nuts.getnext
local getattr            = nuts.getattr
local getlist            = nuts.getlist
local getbox             = nuts.getbox
local getfield           = nuts.getfield

local setfield           = nuts.setfield

local traverse_id        = nuts.traverse_id
local traverse           = nuts.traverse
local copy_node          = nuts.copy
local hpack_node         = nuts.hpack
local insert_node_after  = nuts.insert_after
local insert_node_before = nuts.insert_before
local is_display_math    = nuts.is_display_math
local leftmarginwidth    = nuts.leftmarginwidth

local negated_glue       = nuts.pool.negatedglue
local new_hlist          = nuts.pool.hlist

local ctx_convertnumber  = context.convertnumber
local ctx_makelinenumber = context.makelinenumber

-- cross referencing

function lines.number(n)
    n = tonumber(n)
    local cr = cross_references[n] or 0
    cross_references[n] = nil
    return cr
end

local function resolve(n,m) -- we can now check the 'line' flag (todo)
    while n do
        local id = getid(n)
        if id == whatsit_code then -- why whatsit
            local a = getattr(n,a_linereference)
            if a then
                cross_references[a] = m
            end
        elseif id == hlist_code or id == vlist_code then
            resolve(getlist(n),m)
        end
        n = getnext(n)
    end
end

function lines.finalize(t)
    local getnumber = lines.number
    for _,p in next, t do
        for _,r in next, p do
            local m = r.metadata
            if m and m.kind == "line" then
                local e = r.entries
                local u = r.userdata
                e.linenumber = getnumber(e.text or 0) -- we can nil e.text
                e.conversion = u and u.conversion
                r.userdata = nil -- hack
            end
        end
    end
end

local filters = structures.references.filters
local helpers = structures.helpers

structures.references.registerfinalizer(lines.finalize)

filters.line = filters.line or { }

function filters.line.default(data)
--  helpers.title(data.entries.linenumber or "?",data.metadata)
    ctx_convertnumber(data.entries.conversion or "numbers",data.entries.linenumber or "0")
end

function filters.line.page(data,prefixspec,pagespec) -- redundant
    helpers.prefixpage(data,prefixspec,pagespec)
end

function filters.line.linenumber(data) -- raw
    context(data.entries.linenumber or "0")
end

-- boxed variant, todo: use number mechanism

lines.boxed = { }
local boxed = lines.boxed

-- todo: cache setups, and free id no longer used
-- use interfaces.cachesetup(t)

function boxed.register(configuration)
    last = last + 1
    data[last] = configuration
    if trace_numbers then
        report_lines("registering setup %a",last)
    end
    return last
end

implement {
    name      = "registerlinenumbering",
    actions   = { boxed.register, context },
    arguments = {
        {
            { "continue" },
            { "start", "integer" },
            { "step", "integer" },
            { "method" },
            { "tag" },
        }
    }
}

function boxed.setup(n,configuration)
    local d = data[n]
    if d then
        if trace_numbers then
            report_lines("updating setup %a",n)
        end
        for k,v in next, configuration do
            d[k] = v
        end
    else
        if trace_numbers then
            report_lines("registering setup %a (br)",n)
        end
        data[n] = configuration
    end
    return n
end

implement {
    name      = "setuplinenumbering",
    actions   = boxed.setup,
    arguments = {
        "integer",
        {
            { "continue" },
            { "start", "integer" },
            { "step", "integer" },
            { "method" },
            { "tag" },
        }
    }
}

local function check_number(n,a,skip,sameline)
    local d = data[a]
    if d then
        local tag, skipflag, s = d.tag or "", 0, d.start or 1
        current_list[#current_list+1] = { n, s }
        if sameline then
            skipflag = 0
            if trace_numbers then
                report_lines("skipping broken line number %s for setup %a: %s (%s)",#current_list,a,s,d.continue or v_no)
            end
        elseif not skip and s % d.step == 0 then
            skipflag, d.start = 1, s + 1 -- (d.step or 1)
            if trace_numbers then
                report_lines("making number %s for setup %a: %s (%s)",#current_list,a,s,d.continue or v_no)
            end
        else
            skipflag, d.start = 0, s + 1 -- (d.step or 1)
            if trace_numbers then
                report_lines("skipping line number %s for setup %a: %s (%s)",#current_list,a,s,d.continue or v_no)
            end
        end
        ctx_makelinenumber(tag,skipflag,s,getfield(n,"shift"),getfield(n,"width"),leftmarginwidth(getlist(n)),getfield(n,"dir"))
    end
end

-- xlist
--   xlist
--     hlist

local function identify(list)
    if list then
        for n in traverse_id(hlist_code,list) do
            local a = getattr(n,a_linenumber)
            if a then
                return list, a
            end
        end
        local n = list
        while n do
            local id = getid(n)
            if id == hlist_code or id == vlist_code then
                local ok, a = identify(getlist(n))
                if ok then
                    return ok, a
                end
            end
            n = getnext(n)
        end
    end
end

function boxed.stage_zero(n)
    return identify(getlist(getbox(n)))
end

-- reset ranges per page
-- store first and last per page
-- maybe just set marks directly

function boxed.stage_one(n,nested)
    current_list = { }
    local box = getbox(n)
    if box then
        local found = nil
        local list  = getlist(box)
        if list and nested then
            list, found = identify(list)
        end
        if list then
            local last_a, last_v, skip = nil, -1, false
            for n in traverse_id(hlist_code,list) do -- attr test here and quit as soon as zero found
                if getfield(n,"height") == 0 and getfield(n,"depth") == 0 then
                    -- skip funny hlists -- todo: check line subtype
                else
                    local list = getlist(n)
                    local a = getattr(list,a_linenumber)
                    if not a or a == 0 then
                        local n = getnext(list)
                        while n do
                            local id = getid(n)
                            if id == whatsit_code and getsubtype(n) == textdir_code then
                                n = getnext(n)
                            elseif id == glue_code and getsubtype(n) == leftskip_code then
                                n = getnext(n)
                            else
if id == glyph_code then
                                break
else
    -- can be hlist or skip (e.g. footnote line)
    n = getnext(n)
end
                            end
                        end
                        a = n and getattr(n,a_linenumber)
                    end
                    if a and a > 0 then
                        if last_a ~= a then
                            local da = data[a]
                            local ma = da.method
                            if ma == v_next then
                                skip = true
                            elseif ma == v_page then
                                da.start = 1 -- eventually we will have a normal counter
                            end
                            last_a = a
                            if trace_numbers then
                                report_lines("starting line number range %s: start %s, continue %s",a,da.start,da.continue or v_no)
                            end
                        end
                        if getattr(n,a_displaymath) then
                            if is_display_math(n) then
                                check_number(n,a,skip)
                            end
                        else
                            local v = getattr(list,a_verbatimline)
                            if not v or v ~= last_v then
                                last_v = v
                                check_number(n,a,skip)
                            else
                                check_number(n,a,skip,true)
                            end
                        end
                        skip = false
                    end
                end
            end
        end
    end
end

-- [dir][leftskip][content]

function boxed.stage_two(n,m)
    if #current_list > 0 then
        m = m or lines.scratchbox
        local t, tn = { }, 0
        for l in traverse_id(hlist_code,getlist(getbox(m))) do
            tn = tn + 1
            t[tn] = copy_node(l) -- use take_box instead
        end
        for i=1,#current_list do
            local li = current_list[i]
            local n, m, ti = li[1], li[2], t[i]
            if ti then
                local l = getlist(n)
                -- we want to keep leftskip at the start
--                 local id = getid(l)
--                 if id == whatsit_code and getsubtype(l) == textdir_code then
--                     l = getnext(l)
--                     id = getid(l)
--                 end
--                 if getid(l) == glue_code and getsubtype(l) == leftskip_code then
--                     -- [leftskip] [number] [rest]
--                     local forward  = copy_node(l)
--                     local backward = negated_glue(l)
--                     local next     = getnext(l)
--                     setfield(l,"next",backward)
--                     setfield(backward,"prev",l)
--                     setfield(backward,"next",ti)
--                     setfield(ti,"prev",backward)
--                     setfield(ti,"next",forward)
--                     setfield(forward,"prev",ti)
--                     setfield(forward,"next",next)
--                     setfield(next,"prev",forward)
--                 else
                    -- [number] [rest]
                    setfield(ti,"next",l)
                    setfield(l,"prev",ti)
                    setfield(n,"list",ti)
--                 end
                resolve(n,m)
            else
                report_lines("error in linenumbering (1)")
                return
            end
       end
    end
end

function boxed.stage_two(n,m)
    if #current_list > 0 then
        m = m or lines.scratchbox
        local t, tn = { }, 0
        for l in traverse_id(hlist_code,getlist(getbox(m))) do
            tn = tn + 1
            t[tn] = copy_node(l) -- use take_box instead
        end
        for i=1,#current_list do
            local li = current_list[i]
            local n, m, ti = li[1], li[2], t[i]
            if ti then
                local l = getlist(n)
                setfield(ti,"next",l)
                setfield(l,"prev",ti)
                local h = copy_node(n)
                setfield(h,"dir","TLT")
                setfield(h,"list",ti)
                setfield(n,"list",h)
                resolve(n,m)
            else
                report_lines("error in linenumbering (1)")
                return
            end
       end
    end
end

implement {
    name      = "linenumbersstageone",
    actions   = boxed.stage_one,
    arguments = { "integer", "boolean" }
}

implement {
    name      = "linenumbersstagetwo",
    actions   = boxed.stage_two,
    arguments = { "integer", "integer" }
}
