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

local next, type = next, type
local osexit = os.exit
local sortedhash = table.sortedhash

-- We start with a registration system for atributes so that we can use the symbolic
-- names later on.

local nodes             = nodes
local context           = context
local storage           = storage

local implement         = interfaces.implement

attributes              = attributes or { }
local attributes        = attributes

local sharedstorage     = storage.shared

local texsetattribute   = tex.setattribute

attributes.names        = attributes.names    or { }
attributes.numbers      = attributes.numbers  or { }
attributes.list         = attributes.list     or { }
attributes.values       = attributes.values   or { }
attributes.counts       = attributes.counts   or { }
attributes.handlers     = attributes.handlers or { }
attributes.states       = attributes.states   or { }
attributes.unsetvalue   = tex.magicconstants.unusedattributevalue

local currentfont       = font.current
local currentattributes = nodes and                nodes.     currentattributes or node.currentattributes
local getusedattributes = nodes and nodes.nuts and nodes.nuts.getusedattributes or node.direct.getusedattributes

local names             = attributes.names
local numbers           = attributes.numbers
local list              = attributes.list
local values            = attributes.values
local counts            = attributes.counts

storage.register("attributes/names",   names,   "attributes.names")
storage.register("attributes/numbers", numbers, "attributes.numbers")
storage.register("attributes/list",    list,    "attributes.list")
storage.register("attributes/values",  values,  "attributes.values")
storage.register("attributes/counts",  counts,  "attributes.counts")

local report_attribute   = logs.reporter("attributes")
local report_value       = logs.reporter("attributes","values")

local trace_values       = false

local max_register_index <const> = tex.magicconstants.max_attribute_register_index

trackers.register("attributes.values", function(v) trace_values = v end)

-- function attributes.define(name,number) -- at the tex end
--     if not numbers[name] then
--         numbers[name] = number
--         names[number] = name
--         list[number]  = { }
--     end
-- end

-- We reserve this one as we really want it to be always set (faster).

names[0], numbers["fontdynamic"] = "fontdynamic", 0

-- Private attributes are used by the system and public ones are for users. We use
-- dedicated ranges of numbers for them. Of course a the TeX end a private attribute
-- can be accessible too, so a private attribute can have a public appearance.

sharedstorage.attributes_last_private = sharedstorage.attributes_last_private or   15 -- very private
sharedstorage.attributes_last_public  = sharedstorage.attributes_last_public  or 1024 -- less private

function attributes.private(name) -- at the lua end (hidden from user)
    local number = numbers[name]
    if not number then
        local last = sharedstorage.attributes_last_private
        if last < 1023 then
            last = last + 1
            sharedstorage.attributes_last_private = last
        else
            report_attribute("no more room for private attributes")
            osexit()
        end
        number = last
        numbers[name], names[number], list[number] = number, name, { }
    end
    return number
end

function attributes.public(name) -- at the lua end (hidden from user)
    local number = numbers[name]
    if not number then
        local last = sharedstorage.attributes_last_public
        if last < max_register_index then
            last = last + 1
            sharedstorage.attributes_last_public = last
        else
            report_attribute("no more room for public attributes")
            osexit()
        end
        number = last
        numbers[name], names[number], list[number] = number, name, { }
    end
    return number
end

attributes.system = attributes.private

function attributes.define(name,category)
    return (attributes[category or "public"] or attributes["public"])(name)
end

-- tracers

local function showlist(what,list)
    if list then
        local a = list.next
        local i = 0
        while a do
            local number = a.index
            local value  = a.value
            i = i + 1
            report_attribute("%S %2i: attribute %3i, value %4i, name %a",what,i,number,value,names[number])
            a = a.next
        end
   end
end

function attributes.showcurrent()
    showlist("current",currentattributes())
end

function attributes.ofnode(n)
    showlist(n,n.attr)
end

-- rather special (can be optimized)

local store = { }

function attributes.save(name)
    name = name or ""
    local n = currentattributes()
    n = n and n.next
    local t = { }
    while n do
        t[n.index] = n.value
        n = n.next
    end
    store[name] = {
        attr = t,
        font = currentfont(),
    }
end

function attributes.restore(name)
    name = name or ""
    local t = store[name]
    if t then
        local attr = t.attr
        local font = t.font
        if attr then
            for k, v in next, attr do
                texsetattribute(k,v)
            end
        end
        if font then
         -- tex.font = font
         -- context.getvalue(fonts.hashes.csnames[font])
            currentfont(font)
        end
    end
 -- store[name] = nil
end

-- value manager

local cleaners = { }

-- function attributes.registervalue(index,value)
--     local list = values[index]
--     local last
--     if list then
--         last = counts[index] + 1
--         list[last] = value
--     else
--         last = 1
--         values[index] = { value }
--     end
--     counts[index] = last
--     return last
-- end

function attributes.registervalue(index,value)
    local list = values[index]
    local last
    if list then
        local c = counts[index]
        if c and c[2] > 0 then
            -- this can be an option
            for i=c[1],c[2] do
                if list[i] == nil then
                    -- we avoid 0 because that can be a signal attribute value
                    local n = i == 0 and 1 or i
                    if trace_values then
                        report_value("reusing slot %i for attribute %i in range (%i,%i)",n,index,c[1],c[2])
                    end
                    c[1] = n
                    list[n] = value
                    return n
                end
            end
        else
            c = { 0, 0 }
        end
        last = c[2] + 1
        list[last] = value
        c[1] = last
        c[2] = last
        if trace_values then
            report_value("expanding to slot %i for attribute %i",last,index)
        end
    else
        last = 1
        values[index] = { value }
        counts[index] = { last, last }
        if trace_values then
            report_value("starting at slot %i for attribute %i",last,index)
        end
    end
    return last
end

function attributes.getvalue(index,value)
    local list = values[index]
    return list and list[value] or nil
end

function attributes.hasvalues(index)
    local list = values[index]
    return list and next(list) and true or false
end

function attributes.getvalues(index)
    local list = values[index]
    return list and next(list) and list or nil
end

function attributes.setcleaner(index,cleaner)
    cleaners[index] = cleaner
end

function attributes.checkvalues()
-- if true then
--     report_value("no checking done")
--     return
-- end
     if next(values) then
        local active = getusedattributes()
        if trace_values then
            -- sorted
            for index, list in sortedhash(values) do
                local b = active[index]
                if b then
                    local cleaner = cleaners[index]
                    for k in sortedhash(list) do
                        if b[k] then
                            report_value("keeping value %i for attribute %i",k,index)
                        else
                            report_value("wiping value %i for attribute %i",k,index)
                            if cleaner then
                                cleaner(list[k])
                            end
                            list[k] = nil
                        end
                    end
                    if next(list) then
                        counts[index][1] = 0
                        goto continue
                    end
                end
                report_value("no more values for attribute %i",index)
                values[index] = nil
                counts[index] = nil
                ::continue::
            end
        else
            for index, list in next, values do
                local b = active[index]
                if b then
                    local cleaner = cleaners[index]
                    for k in next, list do
                        if not b[k] then
                            if cleaner then
                                cleaner(list[k])
                            end
                            list[k] = nil
                        end
                    end
                    if next(list) then
                        counts[index][1] = 0
                        goto continue
                    end
                end
                values[index] = nil
                counts[index] = { 0, 0 }
                ::continue::
            end
        end
    elseif trace_values then
        report_value("no check needed")
    end
end

implement {
    name      = "cleanupattributes",
 -- public    = true, -- some day ... but then also \shipoutpage
    protected = true,
    actions   = attributes.checkvalues,
}

-- interface

implement {
    name      = "defineattribute",
    arguments = "2 strings",
    actions   = { attributes.define, context }
}

implement {
    name      = "showattributes",
    actions   = attributes.showcurrent
}

implement {
    name      = "savecurrentattributes",
    arguments = "string",
    actions   = attributes.save
}

implement {
    name      = "restorecurrentattributes",
    arguments = "string",
    actions   = attributes.restore
}
