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"
}

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

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

local variables          = interfaces.variables

local v_next             <const> = variables.next
local v_page             <const> = variables.page
local v_no               <const> = variables.no

local properties         = nodes.properties.data

local nodecodes          = nodes.nodecodes
local hlist_code         <const> = nodecodes.hlist
local vlist_code         <const> = nodecodes.vlist
local whatsit_code       <const> = nodecodes.whatsit
local glyph_code         <const> = nodecodes.glyph

local linelist_code      <const> = nodes.listcodes.line

local latelua_code       <const> = nodes.whatsitcodes.latelua

local localtexrun        = tex.runlocal

local a_linenumber       <const> = attributes.private('linenumber')
local a_linereference    <const> = attributes.private('linereference')

local cross_references   = { }

local nuts               = nodes.nuts

local getid              = nuts.getid
local getattr            = nuts.getattr
local getlist            = nuts.getlist
local getbox             = nuts.getbox
local gettotal           = nuts.gettotal

local takebox            = nuts.takebox

local nexthlist          = nuts.traversers.hlist
local nextvlist          = nuts.traversers.vlist
local nextcontent        = nuts.traversers.content
local nextlist           = nuts.traversers.list
local nextnode           = nuts.traversers.node
local nextwhatsit        = nuts.traversers.whatsit

local findattribute      = nuts.findattribute

local is_display_math    = nuts.is_display_math

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

local paragraphs         = typesetters.paragraphs
local addtoline          = paragraphs.addtoline
local checkline          = paragraphs.checkline

-- cross referencing

function lines.number(n)
    n = tonumber(n)
    local cr = cross_references[n] or 0
    cross_references[n] = nil
    return cr
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)
    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

local boxed = lines.boxed or { }
lines.boxed = 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 resolve(n,m) -- we can now check the 'line' flag (todo)
    for current, id, subtype, content in nextlist, n do
        if content then
            if id == hlist_code then
                for current, subtype in nextwhatsit, content do
                    if subtype == latelua_code then            -- this has to change!
                        local a = getattr(current,a_linereference)
                        if a then
                            cross_references[a] = m
                        end
                    end
                end
            end
            resolve(content,m)
        end
    end
end

local function check_number(n,b,a,skip)
    local d = data[a]
    if d then
        local tag  = d.tag or ""
        local s    = d.start or 1
        local okay = not skip and s % d.step == 0
        if trace_numbers then
            report_lines("%s number for setup %a, tag %a, line %s, continued %a",okay and "making" or "skipping",a,tag,s,d.continue or v_no)
        end
        if okay then
            local p = checkline(n)
            if p then
                localtexrun(function() ctx_makelinenumber(tag,s,p.hsize,p.reverse and 1 or 0) end)
                local l = takebox(b)
                if l then
                    addtoline(n,l)
                else
                    -- very unlikely
                end
            end
        end
        resolve(n,s)
        d.start  = s + 1
    end
end

-- print(nodes.idstostring(list))

-- hlists of type line will only have an attribute when the line number attribute
-- still set at par building time which is not always the case unless we explicitly
-- do a par before we end the line

local function lineisnumbered(n) -- content node
 -- for n, id in nextcontent, getlist(n) do
 --     local a = getattr(n,a_linenumber)
 --     if a and a > 0 then
 --         return a
 --     end
 -- end
    local a = findattribute(getlist(n),a_linenumber,true)
    if a and a > 0 then
        return a
    end
end

local function listisnumbered(list)
    if list then
        for n, subtype in nexthlist, list do
            if subtype == linelist_code then
                local a = getattr(n,a_linenumber)
                if a then
                    -- a quick test for lines (only valid when \par before \stoplinenumbering)
                    return a > 0 and list or false
                else
                    -- a bit slower one, assuming that we have normalized and anchored
                    if lineisnumbered(n) then
                        return list
                    end
                end
            end
        end
    end
end

local function findnumberedlist(list)
    -- we assume wrapped boxes, only one with numbers
    for n, id, subtype, content in nextcontent, list do
        if id == hlist_code then
            if subtype == linelist_code then
                local a = getattr(n,a_linenumber)
                if a then
                    return a > 0 and list
                end
                return
            else
                if lineisnumbered(content) then
                    return n
                end
                local okay = findnumberedlist(content)
                if okay then
                    return okay
                end
            end
        elseif id == vlist_code then
            if listisnumbered(content) then
                return content
            end
            local okay = findnumberedlist(content)
            if okay then
                return okay
            end
        elseif id == glyph_code then
            return
        end
    end
end

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

local function findcolumngap(list)
    -- we assume wrapped boxes, only one with numbers
    for n, id, subtype, content in nextlist, list do
        if id == hlist_code or id == vlist_code then
            local p = properties[n]
            if p and p.columngap then
                if trace_numbers then
                    report_lines("first column gap %a",p.columngap)
                end
                return n
            elseif content then
                local okay = findcolumngap(content)
                if okay then
                    return okay
                end
            end
        end
    end
end

function boxed.addlinenumbers(n,b,nested)
    local box = getbox(n)
    if not box then
        return
    end
    local list = getlist(box)
    if not list then
        return
    end
    local last_a = nil
    local last_v = -1
    local skip   = false

    local function check()
        for n, subtype in nexthlist, list do
            if subtype ~= linelist_code then
                -- go on
            elseif gettotal(n) == 0 then
                -- skip funny hlists -- todo: check line subtype
            else
                local a = lineisnumbered(n)
                if a 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
                    check_number(n,b,a,skip)
                    skip = false
                end
            end
        end
    end

    if nested == 0 then
        if list then
            check()
        end
    elseif nested == 1 then
        local id = getid(box)
        if id == vlist_code then
            if listisnumbered(list) then
                -- ok
            else
                list = findnumberedlist(list)
            end
        else -- hlist
            list = findnumberedlist(list)
        end
        if list then
            check()
        end
    elseif nested == 2 then
        list = findcolumngap(list)
        -- we assume we have a vlist
        if not list then
            return
        end
        for n in nextvlist, list do
            local p = properties[n]
            if p and p.columngap then
                if trace_numbers then
                    report_lines("found column gap %a",p.columngap)
                end
                list = getlist(n)
                if list then
                    check()
                end
            end
        end
    else
        -- bad call
    end
end

-- column attribute

implement {
    name      = "addlinenumbers",
    actions   = boxed.addlinenumbers,
    arguments = "3 integers",
}
