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

-- when all datastructures are stable a packer will be added which will
-- bring down memory consumption a bit; we can use for instance a pagenumber,
-- section, metadata cache (internal then has to move up one level) or a
-- shared cache [we can use a fast and stupid serializer]

local format, tonumber = string.format, tonumber
local texsprint, texprint, texwrite, texcount = tex.sprint, tex.print, tex.write, tex.count
local insert, remove = table.insert, table.remove

local ctxcatcodes = tex.ctxcatcodes

structure.lists     = structure.lists     or { }
structure.sections  = structure.sections  or { }
structure.helpers   = structure.helpers   or { }
structure.documents = structure.documents or { }
structure.pages     = structure.pages     or { }

local lists     = structure.lists
local sections  = structure.sections
local helpers   = structure.helpers
local documents = structure.documents
local pages     = structure.pages

lists.collected = lists.collected or { }
lists.tobesaved = lists.tobesaved or { }
lists.enhancers = lists.enhancers or { }
lists.internals = lists.internals or { }
lists.ordered   = lists.ordered   or { }

local variables = interfaces.variables

local function initializer()
    -- create a cross reference between internal references
    -- and list entries
    local collected = lists.collected
    local internals = jobreferences.internals
    local ordered   = lists.ordered
    for i=1,#collected do
        local c = collected[i]
        local m = c.metadata
        local r = c.references
        if m then
            -- access by internal reference
            local internal = r and r.internal
            if internal then
                internals[internal] = c
            end
            -- access by order in list
            local kind, name = m.kind, m.name
            if kind and name then
                local ok = ordered[kind]
                if ok then
                    local on = ok[name]
                    if on then
                        on[#on+1] = c
                    else
                        ok[name] = { c }
                    end
                else
                    ordered[kind] = { [name] = { c } }
                end
            end
        end
    end
end

if job then
    job.register('structure.lists.collected', structure.lists.tobesaved, initializer)
end

local cached = { }
local pushed = { }

function lists.push(t)
    local r = t.references
    local i = (r and r.internal) or 0 -- brrr
    local p = pushed[i]
    if not p then
        p = #cached + 1
        cached[p] = helpers.simplify(t)
        pushed[i] = p
    end
    texwrite(p)
end

function lists.doifstoredelse(n)
    commands.doifelse(cached[tonumber(n)])
end

-- this is the main pagenumber enhancer

function lists.enhance(n)
 -- todo: symbolic names for counters
    local l = cached[n]
    if l then
        --
        l.directives = nil -- might change
        -- save in the right order (happen sat shipout)
        lists.tobesaved[#lists.tobesaved+1] = l
        -- default enhancer (cross referencing)
        l.references.realpage = texcount.realpageno
        -- specific enhancer (kind of obsolete)
        local kind = l.metadata.kind
        local enhancer = kind and lists.enhancers[kind]
        if enhancer then
            enhancer(l)
        end
    end
end

-- we can use level instead but we can also decide to remove level from the metadata

local nesting = { }

function lists.pushnesting(i)
    local r = lists.result[i]
    local name = r.metadata.name
    local numberdata = r and r.numberdata
    local n = (numberdata and numberdata.numbers[sections.getlevel(name)]) or 0
    insert(nesting, { number = n, name = name, result = lists.result })
end

function lists.popnesting()
    local old = remove(nesting)
    lists.result = old.result
end

local function filter_collected(names, criterium, number, collected)
    local numbers, depth = documents.data.numbers, documents.data.depth
    local hash, result, all = { }, { }, not names or names == "" or names == variables.all
    if not all then
        for s in names:gmatch("[^, ]+") do
            hash[s] = true
        end
    end
    if criterium == variables.all or criterium == variables.text then
        for i=1,#collected do
            local v = collected[i]
            local r = v.references
            if r then
                local sectionnumber = (r.section == 0) or jobsections.collected[r.section]
                if sectionnumber then -- and not sectionnumber.hidenumber then
                    local metadata = v.metadata
                    if metadata and not metadata.nolist and (all or hash[metadata.name or false]) then
                        result[#result+1] = v
                    end
                end
            end
        end
    elseif criterium == variables.current then
        for i=1,#collected do
            local v = collected[i]
            local r = v.references
            if r then
                local sectionnumber = jobsections.collected[r.section]
                if sectionnumber then -- and not sectionnumber.hidenumber then
                    local cnumbers = sectionnumber.numbers
                    local metadata = v.metadata
                    if cnumbers then
                        if metadata and not metadata.nolist and (all or hash[metadata.name or false]) and #cnumbers > depth then
                            local ok = true
                            for d=1,depth do
                                local cnd = cnumbers[d]
                                if not (cnd == 0 or cnd == numbers[d]) then
                                    ok = false
                                    break
                                end
                            end
                            if ok then
                                result[#result+1] = v
                            end
                        end
                    end
                end
            end
        end
    elseif criterium == variables.here then
        for i=1,#collected do
            local v = collected[i]
            local r = v.references
            if r then
                local sectionnumber = jobsections.collected[r.section]
                if sectionnumber then -- and not sectionnumber.hidenumber then
                    local cnumbers = sectionnumber.numbers
                    local metadata = v.metadata
                    if cnumbers then
                        if metadata and not metadata.nolist and (all or hash[metadata.name or false]) and #cnumbers >= depth then
                            local ok = true
                            for d=1,depth do
                                local cnd = cnumbers[d]
                                if not (cnd == 0 or cnd == numbers[d]) then
                                    ok = false
                                    break
                                end
                            end
                            if ok then
                                result[#result+1] = v
                            end
                        end
                    end
                end
            end
        end
    elseif criterium == variables.previous then
        for i=1,#collected do
            local v = collected[i]
            local r = v.references
            if r then
                local sectionnumber = jobsections.collected[r.section]
                if sectionnumber then -- and not sectionnumber.hidenumber then
                    local cnumbers = sectionnumber.numbers
                    local metadata = v.metadata
                    if cnumbers then
                        if metadata and not metadata.nolist and (all or hash[metadata.name or false]) and #cnumbers >= depth then
                            local ok = true
                            for d=1,depth-1 do
                                local cnd = cnumbers[d]
                                if not (cnd == 0 or cnd == numbers[d]) then
                                    ok = false
                                    break
                                end
                            end
                            if ok then
                                result[#result+1] = v
                            end
                        end
                    end
                end
            end
        end
    elseif criterium == variables["local"] then -- not yet ok
        local nested = nesting[#nesting]
        if nested then
            return filter_collected(names,nested.name,nested.number,collected)
        elseif sections.autodepth(documents.data.numbers) == 0 then
            return filter_collected(names,variables.all,number,collected)
        else
            return filter_collected(names,variables.current,number,collected)
        end
    else -- sectionname, number
        local depth = sections.getlevel(criterium)
        local number = tonumber(number) or 0
        for i=1,#collected do
            local v = collected[i]
            local r = v.references
            if r then
                local sectionnumber = jobsections.collected[r.section]
                if sectionnumber then -- and not sectionnumber.hidenumber then
                    local cnumbers = sectionnumber.numbers
                    local metadata = v.metadata
                    if cnumbers then
--                      if metadata and not metadata.nolist and (all or hash[metadata.name or false]) and #cnumbers >= depth and cnumbers[depth] == number then
                        if metadata and not metadata.nolist and (all or hash[metadata.name or false]) and #cnumbers >= depth and (number == 0 or cnumbers[depth] == number) then
                            result[#result+1] = v
                        end
                    end
                end
            end
        end
    end
    return result
end

lists.filter_collected = filter_collected

function lists.filter(names, criterium, number)
    return filter_collected(names, criterium, number, lists.collected)
end

lists.result = { }

function lists.process(...)
    lists.result = lists.filter(...)
    for i=1,#lists.result do
        local r = lists.result[i]
        local m = r.metadata
        texsprint(ctxcatcodes,format("\\processlistofstructure{%s}{%s}{%i}",m.name,m.kind,i))
    end
end

function lists.analyze(...)
    lists.result = lists.filter(...)
end

function lists.userdata(name,r,tag) -- to tex
    local str = lists.result[r]
    str = str and str.userdata
    str = str and str[tag]
    if str then
        texsprint(ctxcatcodes,str)
    end
end

function lists.uservalue(name,r,tag,default) -- to lua
    local str = lists.result[r]
    str = str and str.userdata
    str = str and str[tag]
    return str or default
end

function lists.size()
    texprint(#lists.result)
end

function lists.location(name,n)
    local l = lists.result[n]
    texsprint(l.references.internal or n)
end

function lists.sectionnumber(name,n,spec)
    local data = lists.result[n]
    local sectiondata = jobsections.collected[data.references.section]
    -- hm, prefixnumber?
    sections.typesetnumber(sectiondata,"prefix",spec,sectiondata) -- data happens to contain the spec too
end

-- some basics (todo: helpers for pages)

function lists.title(name,n,tag) -- tag becomes obsolete
    local data = lists.result[n]
    if data then
        local titledata = data.titledata
        if titledata then
            helpers.title(titledata[tag] or titledata.list or titledata.title or "",data.metadata)
--~             texsprint(ctxcatcodes,titledata[tag] or titledata.list or titledata.title or "")
        end
    end
end

function lists.savedtitle(name,n,tag)
    local data = cached[tonumber(n)]
    if data then
        local titledata = data.titledata
        if titledata then
            helpers.title(titledata[tag] or titledata.title or "",data.metadata)
--~             texsprint(ctxcatcodes,titledata[tag] or titledata.title or "")
        end
    end
end

function lists.savednumber(name,n)
    local data = cached[tonumber(n)]
    if data then
        local numberdata = data.numberdata
        if numberdata then
            sections.typesetnumber(numberdata,"number",numberdata or false)
        end
    end
end

function lists.savedprefixednumber(name,n)
    local data = cached[tonumber(n)]
    if data then
        helpers.prefix(data,data.prefixdata)
        local numberdata = data.numberdata
        if numberdata then
            sections.typesetnumber(numberdata,"number",numberdata or false)
        end
    end
end

function lists.prefix(name,n,spec)
    helpers.prefix(lists.result[n],spec)
end

function lists.page(name,n,pagespec)
    helpers.page(lists.result[n],pagespec)
end

function lists.prefixedpage(name,n,prefixspec,pagespec)
    helpers.prefixpage(lists.result[n],prefixspec,pagespec)
end

function lists.realpage(name,n)
    local data = lists.result[n]
    if data then
        local references = data.references
        texsprint(references and references.realpage or 0)
    else
        texsprint(0)
    end
end

-- numbers stored in entry.numberdata + entry.numberprefix

function lists.number(name,n,spec)
    local data = lists.result[n]
    if data then
        local numberdata = data.numberdata
        if numberdata then
            sections.typesetnumber(numberdata,"number",spec or false,numberdata or false)
        end
    end
end

function lists.prefixednumber(name,n,prefixspec,numberspec)
    local data = lists.result[n]
    if data then
        helpers.prefix(data,prefixspec)
        local numberdata = data.numberdata
        if numberdata then
            sections.typesetnumber(numberdata,"number",spec or false,numberdata or false)
        end
    end
end
