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

if context then
    os.exit()
end

-- We could load a few more of the general context libraries but it would
-- not make plain / latex users more happy I guess. So, we stick to some
-- placeholders.

local match, gmatch, gsub, lower = string.match, string.gmatch, string.gsub, string.lower
local formatters, split, format, dump = string.formatters, string.split, string.format, string.dump
local loadfile, type = loadfile, type
local setmetatable, getmetatable, collectgarbage = setmetatable, getmetatable, collectgarbage
local floor = math.floor
local mkdirp = lfs.mkdirp or lfs.mkdirs

local dummyfunction = function()
end

local dummyreporter = function(c)
    return function(f,...)
        local r = texio.reporter or texio.write_nl
        if f then
            r(c .. " : " .. (formatters or format)(f,...))
        else
            r("")
        end
    end
end

local dummyreport = function(c,f,...)
    local r = texio.reporter or texio.write_nl
    if f then
        r(c .. " : " .. (formatters or format)(f,...))
    else
        r("")
    end
end

statistics = {
    register      = dummyfunction,
    starttiming   = dummyfunction,
    stoptiming    = dummyfunction,
    elapsedtime   = nil,
}

directives = {
    register      = dummyfunction,
    enable        = dummyfunction,
    disable       = dummyfunction,
}

trackers = {
    register      = dummyfunction,
    enable        = dummyfunction,
    disable       = dummyfunction,
}

experiments = {
    register      = dummyfunction,
    enable        = dummyfunction,
    disable       = dummyfunction,
}

storage = { -- probably no longer needed
    register      = dummyfunction,
    shared        = { },
}

logs = {
    new           = dummyreporter,
    reporter      = dummyreporter,
    messenger     = dummyreporter,
    report        = dummyreport,
}

callbacks = {
    register = function(n,f)
        return callback.register(n,f)
    end,
}

utilities = utilities or { }

utilities.storage = utilities.storage or {
    allocate = function(t)
        return t or { }
    end,
    mark     = function(t)
        return t or { }
    end,
}

utilities.parsers = utilities.parsers or {
    -- these are less flexible than in context but ok
    -- for generic purpose
    settings_to_array = function(s)
        return split(s,",")
    end,
    settings_to_hash  = function(s)
        local t = { }
        for k, v in gmatch((gsub(s,"^{(.*)}$", "%1")),"([^%s,=]+)=([^%s,]+)") do
            t[k] = v
        end
        return t
    end,
    settings_to_hash_colon_too  = function(s)
        local t = { }
        for k, v in gmatch((gsub(s,"^{(.*)}$", "%1")),"([^%s,=:]+)[=:]([^%s,]+)") do
            t[k] = v
        end
        return t
    end,
}

characters = characters or {
    data = { }
}

-- we need to cheat a bit here

texconfig.kpse_init = true

resolvers = resolvers or { } -- no fancy file helpers used

local remapper = {
    otf    = "opentype fonts",
    ttf    = "truetype fonts",
    ttc    = "truetype fonts",
    cid    = "cid maps",
    cidmap = "cid maps",
 -- fea    = "font feature files", -- no longer supported
    pfb    = "type1 fonts",        -- needed for vector loading
    afm    = "afm",
    enc    = "enc files",
    lua    = "tex",
}

function resolvers.findfile(name,fileformat)
    name = gsub(name,"\\","/")
    if not fileformat or fileformat == "" then
        fileformat = file.suffix(name)
        if fileformat == "" then
            fileformat = "tex"
        end
    end
    fileformat = lower(fileformat)
    fileformat = remapper[fileformat] or fileformat
    local found = kpse.find_file(name,fileformat)
    if not found or found == "" then
        found = kpse.find_file(name,"other text files")
    end
    return found
end

resolvers.findbinfile = resolvers.findfile

function resolvers.loadbinfile(filename,filetype)
    local data = io.loaddata(filename)
    return true, data, #data
end

function resolvers.resolve(s)
    return s
end

function resolvers.unresolve(s)
    return s
end

-- Caches ... I will make a real stupid version some day when I'm in the
-- mood. After all, the generic code does not need the more advanced
-- ConTeXt features. Cached data is not shared between ConTeXt and other
-- usage as I don't want any dependency at all. Also, ConTeXt might have
-- different needs and tricks added.

--~ containers.usecache = true

caches = { }

local writable  = nil
local readables = { }
local usingjit  = jit

if not caches.namespace or caches.namespace == "" or caches.namespace == "context" then
    caches.namespace = 'generic'
end

do

    -- standard context tree setup

    local cachepaths = kpse.expand_var('$TEXMFCACHE') or ""

    -- quite like tex live or so (the weird $TEXMFCACHE test seems to be needed on miktex)

    if cachepaths == "" or cachepaths == "$TEXMFCACHE" then
        cachepaths = kpse.expand_var('$TEXMFVAR') or ""
    end

    -- this also happened to be used (the weird $TEXMFVAR test seems to be needed on miktex)

    if cachepaths == "" or cachepaths == "$TEXMFVAR" then
        cachepaths = kpse.expand_var('$VARTEXMF') or ""
    end

    -- and this is a last resort (hm, we could use TEMP or TEMPDIR)

    if cachepaths == "" then
        local fallbacks = { "TMPDIR", "TEMPDIR", "TMP", "TEMP", "HOME", "HOMEPATH" }
        for i=1,#fallbacks do
            cachepaths = os.getenv(fallbacks[i]) or ""
            if cachepath ~= "" and lfs.isdir(cachepath) then
                break
            end
        end
    end

    if cachepaths == "" then
        cachepaths = "."
    end

    cachepaths = split(cachepaths,os.type == "windows" and ";" or ":")

    for i=1,#cachepaths do
        local cachepath = cachepaths[i]
        if not lfs.isdir(cachepath) then
            mkdirp(cachepath .. '/') -- needed for texlive and latex
            if lfs.isdir(cachepath) then
                logs.report("system","creating cache path '%s'",cachepath)
            end
        end
        if file.is_writable(cachepath) then
            writable = file.join(cachepath,"luatex-cache")
            lfs.mkdir(writable)
            writable = file.join(writable,caches.namespace)
            lfs.mkdir(writable)
            break
        end
    end

    for i=1,#cachepaths do
        if file.is_readable(cachepaths[i]) then
            readables[#readables+1] = file.join(cachepaths[i],"luatex-cache",caches.namespace)
        end
    end

    if not writable then
        logs.report("system","no writeable cache path, quiting")
        os.exit()
    elseif #readables == 0 then
        logs.report("system","no readable cache path, quiting")
        os.exit()
    elseif #readables == 1 and readables[1] == writable then
        logs.report("system","using cache '%s'",writable)
    else
        logs.report("system","using write cache '%s'",writable)
        logs.report("system","using read cache '%s'",table.concat(readables," "))
    end

end

function caches.getwritablepath(category,subcategory)
    local path = file.join(writable,category)
    lfs.mkdir(path)
    path = file.join(path,subcategory)
    lfs.mkdir(path)
    return path
end

function caches.getreadablepaths(category,subcategory)
    local t = { }
    for i=1,#readables do
        t[i] = file.join(readables[i],category,subcategory)
    end
    return t
end

local function makefullname(path,name)
    if path and path ~= "" then
        return file.addsuffix(file.join(path,name),"lua"), file.addsuffix(file.join(path,name),usingjit and "lub" or "luc")
    end
end

function caches.is_writable(path,name)
    local fullname = makefullname(path,name)
    return fullname and file.is_writable(fullname)
end

function caches.loaddata(readables,name,writable)
    for i=1,#readables do
        local path   = readables[i]
        local loader = false
        local luaname, lucname = makefullname(path,name)
        if lfs.isfile(lucname) then
            logs.report("system","loading luc file '%s'",lucname)
            loader = loadfile(lucname)
        end
        if not loader and lfs.isfile(luaname) then
            -- can be different paths when we read a file database from disk
            local luacrap, lucname = makefullname(writable,name)
            logs.report("system","compiling luc file '%s'",lucname)
            if lfs.isfile(lucname) then
                loader = loadfile(lucname)
            end
            caches.compile(data,luaname,lucname)
            if lfs.isfile(lucname) then
                logs.report("system","loading luc file '%s'",lucname)
                loader = loadfile(lucname)
            else
                logs.report("system","error in loading luc file '%s'",lucname)
            end
            if not loader then
                logs.report("system","loading lua file '%s'",luaname)
                loader = loadfile(luaname)
            else
                logs.report("system","error in loading lua file '%s'",luaname)
            end
        end
        if loader then
            loader = loader()
            collectgarbage("step")
            return loader
        end
    end
    return false
end

function caches.savedata(path,name,data)
    local luaname, lucname = makefullname(path,name)
    if luaname then
        logs.report("system","saving lua file '%s'",luaname)
        table.tofile(luaname,data,true)
        if lucname and type(caches.compile) == "function" then
            os.remove(lucname) -- better be safe
            logs.report("system","saving luc file '%s'",lucname)
            caches.compile(data,luaname,lucname)
        end
    end
end

-- The method here is slightly different from the one we have in context. We
-- also use different suffixes as we don't want any clashes (sharing cache
-- files is not that handy as context moves on faster.)

function caches.compile(data,luaname,lucname)
    local d = io.loaddata(luaname)
    if not d or d == "" then
        d = table.serialize(data,true) -- slow
    end
    if d and d ~= "" then
        local f = io.open(lucname,'wb')
        if f then
            local s = loadstring(d)
            if s then
                f:write(dump(s,true))
            end
            f:close()
        end
    end
end

-- simplfied version:

function table.setmetatableindex(t,f)
    if type(t) ~= "table" then
        f, t = t, { }
    end
    local m = getmetatable(t)
    if f == "table" then
        f = function(t,k) local v = { } t[k] = v return v end
    end
    if m then
        m.__index = f
    else
        setmetatable(t,{ __index = f })
    end
    return t
end

function table.makeweak(t)
    local m = getmetatable(t)
    if m then
        m.__mode = "v"
    else
        setmetatable(t,{ __mode = "v" })
    end
    return t
end

-- helper for plain:

arguments = { }

if arg then
    for i=1,#arg do
        local k, v = match(arg[i],"^%-%-([^=]+)=?(.-)$")
        if k and v then
            arguments[k] = v
        end
    end
end

-- another one

if not number.idiv then
    function number.idiv(i,d)
        return floor(i/d) -- i//d in 5.3
    end
end

-- hook into unicode

local u = unicode and unicode.utf8

if u then

    utf.lower = u.lower
    utf.upper = u.upper
    utf.char  = u.char
    utf.byte  = u.byte
    utf.len   = u.len

    -- needed on font-*

    if lpeg.setutfcasers then
        lpeg.setutfcasers(u.lower,u.upper)
    end

    -- needed on font-otr

    local bytepairs = string.bytepairs
    local utfchar   = utf.char
    local concat    = table.concat

    function utf.utf16_to_utf8_be(s)
        if not s then
            return nil
        elseif s == "" then
            return ""
        end
        local result, r, more = { }, 0, 0
        for left, right in bytepairs(s) do
            if right then
                local now = 256*left + right
                if more > 0 then
                    now = (more-0xD800)*0x400 + (now-0xDC00) + 0x10000
                    more = 0
                    r = r + 1
                    result[r] = utfchar(now)
                elseif now >= 0xD800 and now <= 0xDBFF then
                    more = now
                else
                    r = r + 1
                    result[r] = utfchar(now)
                end
            end
        end
        return concat(result)
    end

    local characters = string.utfcharacters

    function utf.split(str)
        local t, n = { }, 0
        for s in characters(str) do
            n = n + 1
            t[n] = s
        end
        return t
    end

end
