if not modules then modules = { } end modules ['strc-syn'] = {
    version   = 1.001,
    comment   = "companion to str-syn.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 format = string.format
local allocate = utilities.storage.allocate

-- interface to tex end

local context      = context
local sorters      = sorters

local structures   = structures
local synonyms     = structures.synonyms
local tags         = structures.tags

local collected    = allocate()
local tobesaved    = allocate()

local firstofsplit = sorters.firstofsplit
local strip        = sorters.strip
local splitter     = sorters.splitters.utf

synonyms.collected = collected
synonyms.tobesaved = tobesaved

local function initializer()
    collected = synonyms.collected
    tobesaved = synonyms.tobesaved
end

local function finalizer()
    for entry, data in next, tobesaved do
        data.hash = nil
    end
end

job.register('structures.synonyms.collected', tobesaved, initializer, finalizer)

-- todo: allocate becomes metatable

local function allocate(class)
    local d = tobesaved[class]
    if not d then
        d = {
            metadata = {
                language = 'en',
                sorted   = false,
                class    = class
            },
            entries  = {
            },
            hash = {
            }
        }
        tobesaved[class] = d
    end
    return d
end

function synonyms.define(class,kind)
    local data = allocate(class)
    data.metadata.kind = kind
end

function synonyms.register(class,kind,spec)
    local data = allocate(class)
    data.metadata.kind = kind -- runtime, not saved in format (yet)
    if not data.hash[spec.definition.tag or ""] then
        data.entries[#data.entries+1] = spec
        data.hash[spec.definition.tag or ""] = spec
    end
end

function synonyms.registerused(class,tag)
    local data = allocate(class)
    local dht = data.hash[tag]
    if dht then
        dht.definition.used = true
    end
end

function synonyms.synonym(class,tag)
    local data = allocate(class).hash
    local d = data[tag]
    if d then
        local de = d.definition
        de.used = true
        context(de.synonym)
    end
end

function synonyms.meaning(class,tag)
    local data = allocate(class).hash
    local d = data[tag]
    if d then
        local de = d.definition
        de.used = true
        context(de.meaning)
    end
end

synonyms.compare = sorters.comparers.basic -- (a,b)

function synonyms.filter(data,options)
    local result = { }
    local entries = data.entries
    local all = options and options.criterium == interfaces.variables.all
    for i=1,#entries do
        local entry = entries[i]
        if all or entry.definition.used then
            result[#result+1] = entry
        end
    end
    data.result = result
end

function synonyms.prepare(data)
    local result = data.result
    if result then
        for i=1, #result do
            local r = result[i]
            local rd = r.definition
            if rd then
                local rt = rd.tag
                local sortkey = rt and rt ~= "" and rt or rd.synonym
                r.split = splitter(strip(sortkey))
            end
        end
    end
end

function synonyms.sort(data,options)
    sorters.sort(data.result,synonyms.compare)
end

function synonyms.finalize(data,options)
    local result = data.result
    data.metadata.nofsorted = #result
    local split = { }
    for k=1,#result do
        local v = result[k]
        local entry, tag = firstofsplit(v)
        local s = split[entry] -- keeps track of change
        local d
        if not s then
            d = { }
            s = { tag = tag, data = d }
            split[entry] = s
        else
            d = s.data
        end
        d[#d+1] = v
    end
    data.result = split
end

-- for now, maybe at some point we will do a multipass or so
-- maybe pass the settings differently

local ctx_synonymentry = context.synonymentry

function synonyms.flush(data,options)
    local kind = data.metadata.kind -- hack, will be done better
    local result = data.result
    local sorted = table.sortedkeys(result)
    for k=1,#sorted do
        local letter = sorted[k]
        local sublist = result[letter]
        local data = sublist.data
        for d=1,#data do
            local entry = data[d].definition
            ctx_synonymentry(d,entry.tag,entry.synonym,entry.meaning or "")
        end
    end
    data.result = nil
    data.metadata.sorted = false
end

function synonyms.analyzed(class,options)
    local data = synonyms.collected[class]
    if data and data.entries then
        options = options or { }
        sorters.setlanguage(options.language)
        synonyms.filter(data,options)   -- filters entries to result
        synonyms.prepare(data,options)  -- adds split table parallel to list table
        synonyms.sort(data,options)     -- sorts entries in result
        synonyms.finalize(data,options) -- do things with data.entries
        data.metadata.sorted = true
    end
    return data and data.metadata.sorted and data.result and next(data.result)
end

function synonyms.process(class,options)
    if synonyms.analyzed(class,options) then
        synonyms.flush(synonyms.collected[class],options)
    end
end

commands.registersynonym     = synonyms.register
commands.registerusedsynonym = synonyms.registerused
commands.synonymmeaning      = synonyms.meaning
commands.synonymname         = synonyms.synonym
commands.processsynonyms     = synonyms.process
