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

local format = string.format
local next, type = next, type
local min, max = math.min, math.max
local texsprint, texcount = tex.sprint, tex.count

local trace_counters = false  trackers.register("structure.counters", function(v) trace_counters = v end)

structure              = structure           or { }
structure.helpers      = structure.helpers   or { }
structure.sections     = structure.sections  or { }
structure.counters     = structure.counters  or { }
structure.documents    = structure.documents or { }

structure.counters          = structure.counters          or { }
structure.counters.data     = structure.counters.data     or { }
structure.counters.specials = structure.counters.specials or { }

local helpers   = structure.helpers
local sections  = structure.sections
local counters  = structure.counters
local documents = structure.documents

local variables = interfaces.variables

-- state: start stop none reset

local counterdata = counters.data
local counterranges, tbs = { }, 0
local counterspecials = counters.specials

counters.collected = counters.collected or { }
counters.tobesaved = counters.tobesaved or { }

storage.register("structure/counters/data", structure.counters.data, "structure.counters.data")
storage.register("structure/counters/tobesaved", structure.counters.tobesaved, "structure.counters.tobesaved")

local collected, tobesaved = counters.collected, counters.tobesaved

local function finalizer()
    local ct = counters.tobesaved
    for name, cd in next, counterdata do
        local cs = tobesaved[name]
        local data = cd.data
        for i=1,#data do
            local d = data[i]
            local r = d.range
            cs[i][r] = d.number
            d.range = r + 1
        end
    end
end

local function initializer()
    collected, tobesaved = counters.collected, counters.tobesaved
end

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

local function constructor(t,s,name,i)
    if s == "last" then
        local cc = collected[name]
        t.stop = (cc and cc[i] and cc[i][t.range]) or 0 -- stop is available for diagnostics purposes only
        if t.offset then
            return t.stop - t.step
        else
            return t.stop
        end
    elseif s == "first" then
        if t.start > 0 then
            return t.start -- brrr
        elseif t.offset then
            return t.start + t.step + 1
        else
            return t.start + 1
        end
    elseif s == "prev" or s == "previous" then
        return max(t.first,t.number-1) -- todo: step
    elseif s == "next" then
        return min(t.last,t.number+1) -- todo: step
    elseif s == "backward" then
        if t.number - 1 < t.first then
            return t.last
        else
            return t.previous
        end
    elseif s == "forward" then
        if t.number + 1 > t.last then
            return t.first
        else
            return t.next
        end
    elseif s == "subs" then
        local cc = collected[name]
        t.subs = (cc and cc[i+1] and cc[i+1][t.range]) or 0
        return t.subs
    else
        return nil -- was 0, but that is fuzzy in testing for e.g. own
    end
end

local enhance = function()
    for name, cd in next, counterdata do
        local data = cd.data
        for i=1,#data do
            local ci = data[i]
            setmetatable(ci, { __index = function(t,s) return constructor(t,s,name,i) end })
        end
    end
    enhance = nil
end

local function allocate(name,i)
    local cd = counterdata[name]
    if not cd then
        cd = {
            level = 1,
--~ block = "", -- todo
            numbers = nil,
            state = variables.start, -- true
            data = { }
        }
        tobesaved[name] = { }
        counterdata[name] = cd
    end
    cd = cd.data
    local ci = cd[i]
    if not ci then
        ci = {
            number = 0,
            start = 0,
            saved = 0,
            step = 1,
            range = 1,
            offset = false,
        --  via metatable: last, first, and for tracing:
            stop = 0,
        }
        setmetatable(ci, { __index = function(t,s) return constructor(t,s,name,i) end })
        cd[i] = ci
        tobesaved[name][i] = { }
    else
        if enhance then enhance() end -- not stored in bytecode
    end
    return ci
end

function counters.record(name,i)
    return allocate(name,i or 1)
end

local function savevalue(name,i)
    if name then
        local cd = counterdata[name].data[i]
        local cs = tobesaved[name][i]
        local cc = collected[name]
        local cr = cd.range
        local old = (cc and cc[i] and cc[i][cr]) or 0
        cs[cr] = cd.number
        cd.range = cr + 1
        return old
    else
        return 0
    end
end

function counters.define(name, start, counter) -- todo: step
    local d = allocate(name,1)
    d.start = start
    if counter ~= "" then
        d.counter = counter -- only for special purposes, cannot be false
    end
end

function counters.trace(name)
    local cd = counterdata[name]
    if cd then
        texsprint(format("[%s:",name))
        local data = cd.data
        for i=1,#data do
            local d = data[i]
            texsprint(format(" (%s: %s,%s,%s s:%s r:%s)",i,(d.start or 0),d.number or 0,d.last,d.step or 0,d.range or 0))
        end
        texsprint("]")
    end
end

function counters.raw(name)
    return counterdata[name]
end

function counters.compact(name,level,onlynumbers)
    local cd = counterdata[name]
--~ print(name,cd)
    if cd then
        local data = cd.data
        local compact = { }
        for i=1,level or #data do
            local d = data[i]
--~ print(name,i,d.number)
            if d.number ~= 0 then
                compact[i] = (onlynumbers and d.number) or d
            end
        end
--~ print(table.serialize(compact))
        return compact
    end
end

-- depends on when incremented, before or after (driven by d.offset)

function counters.doifelse(name)
    commands.doifelse(counterdata[name])
end

function counters.previous(name,n)
    texsprint(allocate(name,n).previous)
end

function counters.next(name,n)
    texsprint(allocate(name,n).next)
end

counters.prev = counters.previous

function counters.current(name,n)
    texsprint(allocate(name,n).number)
end

function counters.first(name,n)
    texsprint(allocate(name,n).first)
end

function counters.last(name,n)
    texsprint(allocate(name,n).last)
end

function counters.subs(name,n)
    texsprint(counterdata[name].data[n].subs or 0)
end

function counters.setvalue(name,tag,value)
    local cd = counterdata[name]
    if cd then
        cd[tag] = value
    end
end

function counters.setstate(name,value) -- true/false
    value = variables[value]
    if value then
        counters.setvalue(name,"state",value)
    end
end

function counters.setlevel(name,value)
    counters.setvalue(name,"level",value)
end

function counters.setoffset(name,value)
    counters.setvalue(name,"offset",value)
end


local function synchronize(name,d)
    local dc = d.counter
    if dc then
        if trace_counters then
            logs.report("counters","setting counter %s with name %s to %s",dc,name,d.number)
        end
        tex.setcount("global",dc,d.number)
    end
    local cs = counterspecials[name]
    if cs then
        if trace_counters then
            logs.report("counters","invoking special for name %s",name)
        end
        cs()
    end
end

function counters.reset(name,n)
    local cd = counterdata[name]
    if cd then
        for i=n or 1,#cd.data do
            local d = cd.data[i]
            savevalue(name,i)
            d.number = d.start or 0
            d.own = nil
            synchronize(name,d)
        end
        cd.numbers = nil
    end
end

function counters.set(name,n,value)
    local cd = counterdata[name]
    if cd then
        local d = allocate(name,n)
        d.number = value or 0
        d.own = nil
        synchronize(name,d)
    end
end

local function check(name,data,start,stop)
    for i=start or 1,stop or #data do
        local d = data[i]
        savevalue(name,i)
        d.number = d.start or 0
        d.own = nil
        synchronize(name,d)
    end
end

function counters.setown(name,n,value)
    local cd = counterdata[name]
    if cd then
        local d = allocate(name,n)
        d.own = value
        d.number = (d.number or d.start or 0) + (d.step or 0)
        local level = cd.level
        if not level or level == -1 then
            -- -1 is signal that we reset manually
        elseif level > 0 then
            check(name,d,n+1)
        elseif level == 0 then
            -- happens elsewhere
        end
        synchronize(name,d)
    end
end

function counters.restart(name,n,newstart)
    local cd = counterdata[name]
    if cd then
        newstart = tonumber(newstart)
        if newstart then
            local d = allocate(name,n)
            d.start = newstart
            counters.reset(name,n)
        end
    end
end

function counters.save(name) -- or just number
    local cd = counterdata[name]
    if cd then
        cd.saved = table.copy(cd.data)
    end
end

function counters.restore(name)
    local cd = counterdata[name]
    if cd and cd.saved then
        cd.data = cd.saved
        cd.saved = nil
    end
end

function counters.add(name,n,delta)
    local cd = counterdata[name]
    if cd and cd.state == variables.start then
        local data = cd.data
        local d = allocate(name,n)
        d.number = (d.number or d.start or 0) + delta*(d.step or 0)
        local level = cd.level
        if not level or level == -1 then
            -- -1 is signal that we reset manually
        elseif level > 0 then
            -- within countergroup
            check(name,data,n+1)
        elseif level == 0 then
            -- happens elsewhere
        end
        synchronize(name,d)
        return d.number -- not needed
    end
    return 0
end

function counters.check(level) -- not used (yet)
    for name, cd in next, counterdata do
        -- logs.report("counters","%s %s %s",name,cd.level,level)
        if cd.level == level then
            if trace_counters then
                logs.report("counters","resetting %s at level %s",name,level)
            end
            counters.reset(name)
        end
    end
end

function counters.get(name,n,key)
    local d = allocate(name,n)
    d = d and d[key]
    if not d then
        return 0
    elseif type(d) == "function" then
        return d()
    else
        return d
    end
end

function counters.value(name,n) -- what to do with own
    tex.write(counters.get(name,n or 1,'number') or 0)
end

function counters.converted(name,spec) -- name can be number and reference to storage
    local cd
    if type(name) == "number" then
        cd = specials.retrieve("counter",name)
        cd = cd and cd.counter
    else
        cd = counterdata[name]
    end
    if cd then
        local spec = spec or { }
        local numbers, ownnumbers = { }, { }
        local reverse = spec.order == variables.reverse
        local kind = spec.type or "number"
        local v_first, v_next, v_previous, v_last = variables.first, variables.next, variables.previous, variables.last
        local data = cd.data
        for k=1,#data do
            local v = data[k]
            -- somewhat messy, what if subnr? only last must honour kind?
            local vn
            if v.own then
                numbers[k], ownnumbers[k] = v.number, v.own
            else
                if kind == v_first then
                    vn = v.first
                elseif kind == v_next then
                    vn = v.next
                elseif kind == v_previous then
                    vn = v.prev
                elseif kind == v_last then
                    vn = v.last
                else
                    vn = v.number
                    if reverse then
                        local vf = v.first
                        local vl = v.last
                        if vl > 0 then
                        --  vn = vl - vn + 1 + vf
                            vn = vl - vn + vf -- see testbed for test
                        end
                    end
                end
                numbers[k], ownnumbers[k] = vn or v.number, nil
            end
        end
        cd.numbers = numbers
        cd.ownnumbers = ownnumbers
        sections.typesetnumber(cd,'number',spec)
        cd.numbers = nil
        cd.ownnumbers = nil
    end
end

-- move to strc-pag.lua

function counters.analyse(name,counterspecification)
    local cd = counterdata[name]
    -- safeguard
    if not cd then
        return false, false, "no counter data"
    end
    -- section data
    local sectiondata = sections.current()
    if not sectiondata then
        return cd, false, "not in section"
    end
    local references = sectiondata.references
    if not references then
        return cd, false, "no references"
    end
    local section = references.section
    if not section then
        return cd, false, "no section"
    end
    sectiondata = jobsections.collected[references.section]
    if not sectiondata then
        return cd, false, "no section data"
    end
    -- local preferences
    local no = variables.no
    if counterspecification and counterspecification.prefix == no then
        return cd, false, "current spec blocks prefix"
    end
    -- stored preferences (not used)
    if cd.prefix == no then
        return cd, false, "entry blocks prefix"
    end
    -- sectioning
    -- if sectiondata.prefix == no then
    --     return false, false, "sectiondata blocks prefix"
    -- end
    -- final verdict
    return cd, sectiondata, "okay"
end

function counters.prefixedconverted(name,prefixspec,numberspec)
    local cd, prefixdata, result = counters.analyse(name,prefixspec)
    if cd then
        if prefixdata then
            sections.typesetnumber(prefixdata,"prefix",prefixspec or false,cd or false)
        end
        counters.converted(name,numberspec)
    end
end
