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

local allocate, mark = utilities.storage.allocate, utilities.storage.mark

local format, sub, match, gsub, find = string.format, string.sub, string.match, string.gsub, string.find
local unquoted, quoted = string.unquoted, string.quoted
local concat, insert, remove = table.concat, table.insert, table.remove

environment       = environment or { }
local environment = environment

-- precautions

os.setlocale(nil,nil) -- useless feature and even dangerous in luatex

function os.setlocale()
    -- no way you can mess with it
end

-- dirty tricks (we will replace the texlua call by luatex --luaonly)

local validengines = allocate {
    ["luatex"]        = true,
    ["luajittex"]     = true,
 -- ["luatex.exe"]    = true,
 -- ["luajittex.exe"] = true,
}

local basicengines = allocate {
    ["luatex"]        = "luatex",
    ["texlua"]        = "luatex",
    ["texluac"]       = "luatex",
    ["luajittex"]     = "luajittex",
    ["texluajit"]     = "luajittex",
 -- ["texlua.exe"]    = "luatex",
 -- ["texluajit.exe"] = "luajittex",
}

local luaengines = allocate {
    ["lua"]    = true,
    ["luajit"] = true,
}

environment.validengines = validengines
environment.basicengines = basicengines

-- [-1] = binary
-- [ 0] = self
-- [ 1] = argument 1 ...

-- instead we could set ranges

if not arg then
    environment.used_as_library = true
    -- used as library
elseif luaengines[file.removesuffix(arg[-1])] then
--     arg[-1] = arg[0]
--     arg[ 0] = arg[1]
--     for k=2,#arg do
--         arg[k-1] = arg[k]
--     end
--     remove(arg) -- last
--
--    environment.used_as_library = true
elseif validengines[file.removesuffix(arg[0])] then
    if arg[1] == "--luaonly" then
        arg[-1] = arg[0]
        arg[ 0] = arg[2]
        for k=3,#arg do
            arg[k-2] = arg[k]
        end
        remove(arg) -- last
        remove(arg) -- pre-last
    else
        -- tex run
    end

    -- This is an ugly hack but it permits symlinking a script (say 'context') to 'mtxrun' as in:
    --
    --   ln -s /opt/minimals/tex/texmf-linux-64/bin/mtxrun context
    --
    -- The special mapping hack is needed because 'luatools' boils down to 'mtxrun --script base'
    -- but it's unlikely that there will be more of this

    local originalzero   = file.basename(arg[0])
    local specialmapping = { luatools == "base" }

    if originalzero ~= "mtxrun" and originalzero ~= "mtxrun.lua" then
       arg[0] = specialmapping[originalzero] or originalzero
       insert(arg,0,"--script")
       insert(arg,0,"mtxrun")
    end

end

-- environment

environment.arguments   = allocate()
environment.files       = allocate()
environment.sortedflags = nil

-- context specific arguments (in order not to confuse the engine)

function environment.initializearguments(arg)
    local arguments, files = { }, { }
    environment.arguments, environment.files, environment.sortedflags = arguments, files, nil
    for index=1,#arg do
        local argument = arg[index]
        if index > 0 then
            local flag, value = match(argument,"^%-+(.-)=(.-)$")
            if flag then
                flag = gsub(flag,"^c:","")
                arguments[flag] = unquoted(value or "")
            else
                flag = match(argument,"^%-+(.+)")
                if flag then
                    flag = gsub(flag,"^c:","")
                    arguments[flag] = true
                else
                    files[#files+1] = argument
                end
            end
        end
    end
    environment.ownname = file.reslash(environment.ownname or arg[0] or 'unknown.lua')
end

function environment.setargument(name,value)
    environment.arguments[name] = value
end

-- todo: defaults, better checks e.g on type (boolean versus string)
--
-- tricky: too many hits when we support partials unless we add
-- a registration of arguments so from now on we have 'partial'

function environment.getargument(name,partial)
    local arguments, sortedflags = environment.arguments, environment.sortedflags
    if arguments[name] then
        return arguments[name]
    elseif partial then
        if not sortedflags then
            sortedflags = allocate(table.sortedkeys(arguments))
            for k=1,#sortedflags do
                sortedflags[k] = "^" .. sortedflags[k]
            end
            environment.sortedflags = sortedflags
        end
        -- example of potential clash: ^mode ^modefile
        for k=1,#sortedflags do
            local v = sortedflags[k]
            if find(name,v) then
                return arguments[sub(v,2,#v)]
            end
        end
    end
    return nil
end

environment.argument = environment.getargument

function environment.splitarguments(separator) -- rather special, cut-off before separator
    local done, before, after = false, { }, { }
    local originalarguments = environment.originalarguments
    for k=1,#originalarguments do
        local v = originalarguments[k]
        if not done and v == separator then
            done = true
        elseif done then
            after[#after+1] = v
        else
            before[#before+1] = v
        end
    end
    return before, after
end

function environment.reconstructcommandline(arg,noquote)
    arg = arg or environment.originalarguments
    if noquote and #arg == 1 then
        -- we could just do: return unquoted(resolvers.resolve(arg[i]))
        local a = arg[1]
        a = resolvers.resolve(a)
        a = unquoted(a)
        return a
    elseif #arg > 0 then
        local result = { }
        for i=1,#arg do
            -- we could just do: result[#result+1] = format("%q",unquoted(resolvers.resolve(arg[i])))
            local a = arg[i]
            a = resolvers.resolve(a)
            a = unquoted(a)
            a = gsub(a,'"','\\"') -- tricky
            if find(a," ") then
                result[#result+1] = quoted(a)
            else
                result[#result+1] = a
            end
        end
        return concat(result," ")
    else
        return ""
    end
end

-- handy in e.g. package.addluapath(environment.relativepath("scripts"))

function environment.relativepath(path,root)
    if not path then
        path = ""
    end
    if not file.is_rootbased_path(path) then
        if not root then
            root = file.pathpart(environment.ownscript or environment.ownname or ".")
        end
        if root == "" then
            root = "."
        end
        path = root .. "/" .. path
    end
    return file.collapsepath(path,true)
end

-- -- when script lives on e:/tmp we get this:
--
-- print(environment.relativepath("x/y/z","c:/w")) -- c:/w/x/y/z
-- print(environment.relativepath("x"))            -- e:/tmp/x
-- print(environment.relativepath("../x"))         -- e:/x
-- print(environment.relativepath("./x"))          -- e:/tmp/x
-- print(environment.relativepath("/x"))           -- /x
-- print(environment.relativepath("c:/x"))         -- c:/x
-- print(environment.relativepath("//x"))          -- //x
-- print(environment.relativepath())               -- e:/tmp

-- -- to be tested:
--
-- function environment.reconstructcommandline(arg,noquote)
--     arg = arg or environment.originalarguments
--     if noquote and #arg == 1 then
--         return unquoted(resolvers.resolve(arg[1]))
--     elseif #arg > 0 then
--         local result = { }
--         for i=1,#arg do
--             result[#result+1] = format("%q",unquoted(resolvers.resolve(arg[i]))) -- always quote
--         end
--         return concat(result," ")
--     else
--         return ""
--     end
-- end

if arg then

    -- new, reconstruct quoted snippets (maybe better just remove the " then and add them later)
    local newarg, instring = { }, false

    for index=1,#arg do
        local argument = arg[index]
        if find(argument,"^\"") then
            newarg[#newarg+1] = gsub(argument,"^\"","")
            if not find(argument,"\"$") then
                instring = true
            end
        elseif find(argument,"\"$") then
            newarg[#newarg] = newarg[#newarg] .. " " .. gsub(argument,"\"$","")
            instring = false
        elseif instring then
            newarg[#newarg] = newarg[#newarg] .. " " .. argument
        else
            newarg[#newarg+1] = argument
        end
    end
    for i=1,-5,-1 do
        newarg[i] = arg[i]
    end

    environment.initializearguments(newarg)

    environment.originalarguments = mark(newarg)
    environment.rawarguments      = mark(arg)

    arg = { } -- prevent duplicate handling

end