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

-- needs a cleanup (share locals)
-- discard language when redefined

-- 002D : hyphen-minus (ascii)
-- 2010 : hyphen
-- 2011 : nonbreakable hyphen
-- 2013 : endash (compound hyphen)

--~ lang:hyphenation(string) string = lang:hyphenation() lang:clear_hyphenation()

local type, tonumber = type, tonumber
local utfbyte = utf.byte
local format, gsub = string.format, string.gsub
local concat, sortedkeys, sortedpairs = table.concat, table.sortedkeys, table.sortedpairs
local lpegmatch = lpeg.match

local settings_to_array = utilities.parsers.settings_to_array

local trace_patterns = false  trackers.register("languages.patterns", function(v) trace_patterns = v end)

local report_initialization = logs.reporter("languages","initialization")

local prehyphenchar  = lang.prehyphenchar  -- global per language
local posthyphenchar = lang.posthyphenchar -- global per language
local lefthyphenmin  = lang.lefthyphenmin
local righthyphenmin = lang.righthyphenmin

local lang           = lang
lang.exceptions      = lang.hyphenation
local new_langage    = lang.new

languages            = languages or {}
local languages      = languages

languages.version    = 1.010

languages.registered = languages.registered or { }
local registered     = languages.registered

languages.associated = languages.associated or { }
local associated     = languages.associated

languages.numbers    = languages.numbers    or { }
local numbers        = languages.numbers

languages.data       = languages.data       or { }
local data           = languages.data

storage.register("languages/numbers",   numbers,   "languages.numbers")
storage.register("languages/registered",registered,"languages.registered")
storage.register("languages/associated",associated,"languages.associated")
storage.register("languages/data",      data,      "languages.data")

local nofloaded  = 0

local function resolve(tag)
    local data, instance = registered[tag], nil
    if data then
        instance = data.instance
        if not instance then
            instance = new_langage(data.number)
            data.instance = instance
        end
    end
    return data, instance
end

local function tolang(what) -- returns lang object
    local tag = numbers[what]
    local data = tag and registered[tag] or registered[what]
    if data then
        local instance = data.lang
        if not instance then
            instance = new_langage(data.number)
            data.instance = instance
        end
        return instance
    end
end

-- languages.tolang = tolang

-- patterns=en
-- patterns=en,de

local function validdata(dataset,what,tag)
    if dataset then
        local data = dataset.data
        if not data or data == "" then
            return nil
        elseif dataset.compression == "zlib" then
            data = zlib.decompress(data)
            if dataset.length and dataset.length ~= #data then
                report_initialization("compression error in %a for language %a","patterns",what,tag)
            end
            return data
        else
            return data
        end
    end
end

local function loaddefinitions(tag,specification)
    statistics.starttiming(languages)
    local data, instance = resolve(tag)
    local definitions = settings_to_array(specification.patterns or "")
    if #definitions > 0 then
        if trace_patterns then
            report_initialization("pattern specification for language %a: %s",tag,specification.patterns)
        end
        local dataused, ok = data.used, false
        for i=1,#definitions do
            local definition = definitions[i]
            if definition == "" then
                -- error
            elseif definition == "reset" then -- interfaces.variables.reset
                if trace_patterns then
                    report_initialization("clearing patterns for language %a",tag)
                end
                instance:clear_patterns()
            elseif not dataused[definition] then
                dataused[definition] = definition
                local filename = "lang-" .. definition .. ".lua"
                local fullname = resolvers.findfile(filename) or ""
                if fullname == "" then
                    fullname = resolvers.findfile(filename .. ".gz") or ""
                end
                if fullname ~= "" then
                    if trace_patterns then
                        report_initialization("loading definition %a for language %a from %a",definition,tag,fullname)
                    end
                    local suffix, gzipped = gzip.suffix(fullname)
                    local defs = table.load(fullname,gzipped and gzip.load)
                    if defs then -- todo: version test
                        ok, nofloaded = true, nofloaded + 1
                     -- instance:patterns   (defs.patterns   and defs.patterns  .data or "")
                     -- instance:hyphenation(defs.exceptions and defs.exceptions.data or "")
                        instance:patterns   (validdata(defs.patterns,  "patterns",  tag) or "")
                        instance:hyphenation(validdata(defs.exceptions,"exceptions",tag) or "")
                    else
                        report_initialization("invalid definition %a for language %a in %a",definition,tag,filename)
                    end
                elseif trace_patterns then
                    report_initialization("invalid definition %a for language %a in %a",definition,tag,filename)
                end
            elseif trace_patterns then
                report_initialization("definition %a for language %a already loaded",definition,tag)
            end
        end
        return ok
    elseif trace_patterns then
        report_initialization("no definitions for language %a",tag)
    end
    statistics.stoptiming(languages)
end

storage.shared.noflanguages = storage.shared.noflanguages or 0

local noflanguages = storage.shared.noflanguages

function languages.define(tag,parent)
    noflanguages = noflanguages + 1
    if trace_patterns then
        report_initialization("assigning number %a to %a",noflanguages,tag)
    end
    numbers[noflanguages] = tag
    registered[tag] = {
        tag      = tag,
        parent   = parent or "",
        patterns = "",
        loaded   = false,
        used     = { },
        dirty    = true,
        number   = noflanguages,
        instance = nil, -- luatex data structure
        synonyms = { },
    }
    storage.shared.noflanguages = noflanguages
end

function languages.setsynonym(synonym,tag) -- convenience function
    local l = registered[tag]
    if l then
        l.synonyms[synonym] = true -- maybe some day more info
    end
end

function languages.installed(separator)
    return concat(sortedkeys(registered),separator or ",")
end

function languages.current(n)
    return numbers[n and tonumber(n) or tex.language]
end

function languages.associate(tag,script,language) -- not yet used
    associated[tag] = { script, language }
end

function languages.association(tag) -- not yet used
    if type(tag) == "number" then
        tag = numbers[tag]
    end
    local lat = tag and associated[tag]
    if lat then
        return lat[1], lat[2]
    end
end

function languages.loadable(tag,defaultlanguage) -- hack
    local l = registered[tag] -- no synonyms
    if l and resolvers.findfile("lang-"..l.patterns..".lua") then
        return true
    else
        return false
    end
end

-- a bit messy, we will do all language setting in lua as we can now assign
-- and 'patterns' will go away here.

function languages.unload(tag)
    local l = registered[tag]
    if l then
        l.dirty = true
    end
end

if environment.initex then

    function languages.getnumber()
        return 0
    end

else

    function languages.getnumber(tag,default,patterns)
        local l = registered[tag]
        if l then
            if l.dirty then
                if trace_patterns then
                    report_initialization("checking patterns for %a with default %a",tag,default)
                end
                -- patterns is already resolved to parent patterns if applicable
                if patterns and patterns ~= "" then
                    if l.patterns ~= patterns then
                        l.patterns = patterns
                        if trace_patterns then
                            report_initialization("loading patterns for %a using specification %a",tag,patterns)
                        end
                        loaddefinitions(tag,l)
                    else
                        -- unchanged
                    end
                elseif l.patterns == "" then
                    l.patterns = tag
                    if trace_patterns then
                        report_initialization("loading patterns for %a using tag",tag)
                    end
                    local ok = loaddefinitions(tag,l)
                    if not ok and tag ~= default then
                        l.patterns = default
                        if trace_patterns then
                            report_initialization("loading patterns for %a using default",tag)
                        end
                        loaddefinitions(tag,l)
                    end
                end
                l.loaded = true
                l.dirty = false
            end
            return l.number
        else
            return 0
        end
    end

end

-- not that usefull, global values

function languages.prehyphenchar (what) return prehyphenchar (tolang(what)) end
function languages.posthyphenchar(what) return posthyphenchar(tolang(what)) end
function languages.lefthyphenmin (what) return lefthyphenmin (tolang(what)) end
function languages.righthyphenmin(what) return righthyphenmin(tolang(what)) end

-- e['implementer']= 'imple{m}{-}{-}menter'
-- e['manual'] = 'man{}{}{}'
-- e['as'] = 'a-s'
-- e['user-friendly'] = 'user=friend-ly'
-- e['exceptionally-friendly'] = 'excep-tionally=friend-ly'

function languages.loadwords(tag,filename)
    local data, instance = resolve(tag)
    if data then
        statistics.starttiming(languages)
        instance:hyphenation(io.loaddata(filename) or "")
        statistics.stoptiming(languages)
    end
end

function languages.setexceptions(tag,str)
    local data, instance = resolve(tag)
    if data then
        instance:hyphenation(string.strip(str)) -- we need to strip leading spaces
    end
end

function languages.hyphenate(tag,str)
    -- todo: does this still work?
    local data, instance = resolve(tag)
    if data then
        return instance:hyphenate(str)
    else
        return str
    end
end

-- hyphenation.define        ("zerolanguage")
-- hyphenation.loadpatterns  ("zerolanguage") -- else bug
-- hyphenation.loadexceptions("zerolanguage") -- else bug

languages.logger = languages.logger or { }

function languages.logger.report()
    local result, r = { }, 0
    for tag, l in sortedpairs(registered) do
        if l.loaded then
            r = r + 1
            result[r] = format("%s:%s:%s",tag,l.parent,l.number)
        end
    end
    return r > 0 and concat(result," ") or "none"
end

-- must happen at the tex end .. will use lang-def.lua

languages.associate('en','latn','eng')
languages.associate('uk','latn','eng')
languages.associate('nl','latn','nld')
languages.associate('de','latn','deu')
languages.associate('fr','latn','fra')

statistics.register("loaded patterns", function()
    local result = languages.logger.report()
    if result ~= "none" then
--         return result
        return format("%s, load time: %s",result,statistics.elapsedtime(languages))
    end
end)

-- statistics.register("language load time", function()
--     -- often zero so we can merge that in the above
--     return statistics.elapsedseconds(languages, format(", nofpatterns: %s",nofloaded))
-- end)

-- interface

local getnumber = languages.getnumber

function commands.languagenumber(tag,default,patterns)
    context(getnumber(tag,default,patterns))
end

function commands.installedlanguages(separator)
    context(languages.installed(separator))
end

commands.definelanguage        = languages.define
commands.setlanguagesynonym    = languages.setsynonym
commands.unloadlanguage        = languages.unload
commands.setlanguageexceptions = languages.setexceptions
