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

-- in retrospect dealing it's not that bad to deal with the nesting
-- and push/poppign at the tex end

local next, rawget, tostring, tonumber = next, rawget, tostring, tonumber
local gsub, match, find = string.gsub, string.match, string.find
local insert, remove, concat = table.insert, table.remove, table.concat
local validstring, formatters = string.valid, string.formatters
local sortedhash = table.sortedhash
local setmetatableindex, setmetatablenewindex = table.setmetatableindex, table.setmetatablenewindex

local commands          = commands
local resolvers         = resolvers
local context           = context

local ctx_doifelse      = commands.doifelse

local implement         = interfaces.implement

local trace_jobfiles    = false  trackers.register("system.jobfiles", function(v) trace_jobfiles = v end)

local report            = logs.reporter("system")
local report_jobfiles   = logs.reporter("system", "jobfiles")
local report_functions  = logs.reporter("system", "functions")

local texsetcount       = tex.setcount
local elements          = interfaces.elements
local constants         = interfaces.constants
local variables         = interfaces.variables
local settings_to_array = utilities.parsers.settings_to_array
local allocate          = utilities.storage.allocate

local nameonly          = file.nameonly
local suffixonly        = file.suffix
local basename          = file.basename
local addsuffix         = file.addsuffix
local removesuffix      = file.removesuffix
local dirname           = file.dirname
local is_qualified_path = file.is_qualified_path

local cleanpath         = resolvers.cleanpath
local toppath           = resolvers.toppath
local resolveprefix     = resolvers.resolve

local currentfile       = luatex.currentfile

local hasscheme         = url.hasscheme

local jobresolvers      = resolvers.jobs

local registerextrapath = resolvers.registerextrapath
local resetextrapaths   = resolvers.resetextrapaths
local getextrapaths     = resolvers.getextrapath
local pushextrapath     = resolvers.pushextrapath
local popextrapath      = resolvers.popextrapath

----- v_outer           = variables.outer
local v_text            = variables.text
local v_project         = variables.project
local v_environment     = variables.environment
local v_product         = variables.product
local v_component       = variables.component
local v_yes             = variables.yes

-- main code .. there is some overlap .. here we have loc://

local function findctxfile(name) -- loc ? any ?
    if is_qualified_path(name) then -- maybe when no suffix do some test for tex
        return name
    elseif not hasscheme(name) then
        return resolvers.finders.byscheme("loc",name) or ""
    else
        return resolvers.findtexfile(name) or ""
    end
end

resolvers.findctxfile = findctxfile

implement {
    name      = "processfile",
    arguments = "string",
    actions   = function(name)
        name = findctxfile(name)
        if name ~= "" then
            context.input(name)
        end
    end
}

implement {
    name      = "doifelseinputfile",
    arguments = "string",
    actions   = function(name)
        ctx_doifelse(findctxfile(name) ~= "")
    end
}

implement {
    name      = "locatefilepath",
    arguments = "string",
    actions   = function(name)
        context(dirname(findctxfile(name)))
    end
}

implement {
    name      = "usepath",
    arguments = "string",
    actions   = function(paths)
        report_jobfiles("using path: %s",paths)
        registerextrapath(paths)
    end
}

implement {
    name      = "pushpath",
    arguments = "string",
    actions   = function(paths)
        report_jobfiles("pushing path: %s",paths)
        pushextrapath(paths)
    end
}

implement {
    name      = "poppath",
    actions   = function(paths)
        popextrapath()
        report_jobfiles("popping path")
    end
}

implement {
    name      = "usesubpath",
    arguments = "string",
    actions   = function(subpaths)
        report_jobfiles("using subpath: %s",subpaths)
        registerextrapath(nil,subpaths)
    end
}

implement {
    name      = "resetpath",
    actions   = function()
        report_jobfiles("resetting path")
        resetextrapaths()
    end
}

implement {
    name      = "allinputpaths",
    actions   = function()
        context(concat(getextrapaths(),","))
    end
}

implement {
    name      = "usezipfile",
    arguments = "2 strings",
    actions   = function(name,tree)
        if tree and tree ~= "" then
            resolvers.usezipfile(formatters["zip:///%s?tree=%s"](name,tree))
        else
            resolvers.usezipfile(formatters["zip:///%s"](name))
        end
    end
}

-- moved from tex to lua:

local texpatterns = { "%s.mkvi", "%s.mkiv", "%s.mklx", "%s.mkxl", "%s.tex" }
local luapatterns = { "%s" .. utilities.lua.suffixes.luc, "%s.lua", "%s.lmt" }
local cldpatterns = { "%s.cld" }
local xmlpatterns = { "%s.xml" }

local uselibrary = resolvers.uselibrary
local input      = context.input

-- status
--
-- these need to be synced with input stream:

local processstack   = { }
local processedfile  = ""
local processedfiles = { }

implement {
    name    = "processedfile",
    actions = function()
        context(processedfile)
    end
}

implement {
    name    = "processedfiles",
    actions = function()
        context(concat(processedfiles,","))
    end
}

implement {
    name      = "dostarttextfile",
    arguments = "string",
    actions   = function(name)
        insert(processstack,name)
        processedfile = name
        insert(processedfiles,name)
    end
}

implement {
    name      = "dostoptextfile",
    actions   = function()
        processedfile = remove(processstack) or ""
    end
}

local function startprocessing(name,notext)
    if not notext then
     -- report("begin file %a at line %a",name,status.linenumber or 0)
        context.dostarttextfile(name)
    end
end

local function stopprocessing(notext)
    if not notext then
        context.dostoptextfile()
     -- report("end file %a at line %a",name,status.linenumber or 0)
    end
end

--

local typestack   = { }
local currenttype = v_text
local nofmissing  = 0
local missing     = {
    tex = setmetatableindex("number"),
    lua = setmetatableindex("number"),
    cld = setmetatableindex("number"),
    xml = setmetatableindex("number"),
}

local function reportfailure(kind,name)
    nofmissing = nofmissing + 1
    missing[kind][name] = true
    report_jobfiles("unknown %s file %a",kind,name)
end

--

local function action(name,foundname)
    input(foundname)
end
local function failure(name,foundname)
    reportfailure("tex",name)
end
local function usetexfile(name,onlyonce,notext)
    startprocessing(name,notext)
    uselibrary {
        name     = name,
        patterns = texpatterns,
        action   = action,
        failure  = failure,
        onlyonce = onlyonce,
    }
    stopprocessing(notext)
end

local function action(name,foundname)
    dofile(foundname)
end
local function failure(name,foundname)
    reportfailure("lua",name)
end
local function useluafile(name,onlyonce,notext)
    uselibrary {
        name     = name,
        patterns = luapatterns,
        action   = action,
        failure  = failure,
        onlyonce = onlyonce,
    }
end

local function action(name,foundname)
    dofile(foundname)
end
local function failure(name,foundname)
    reportfailure("cld",name)
end
local function usecldfile(name,onlyonce,notext)
    startprocessing(name,notext)
    uselibrary {
        name     = name,
        patterns = cldpatterns,
        action   = action,
        failure  = failure,
        onlyonce = onlyonce,
    }
    stopprocessing(notext)
end

local function action(name,foundname)
    context.xmlprocess(foundname,"main","")
end
local function failure(name,foundname)
    reportfailure("xml",name)
end
local function usexmlfile(name,onlyonce,notext)
    startprocessing(name,notext)
    uselibrary {
        name     = name,
        patterns = xmlpatterns,
        action   = action,
        failure  = failure,
        onlyonce = onlyonce,
    }
    stopprocessing(notext)
end

local suffixes = {
    mkvi = usetexfile,
    mkiv = usetexfile,
    mklx = usetexfile,
    mkxl = usetexfile,
    tex  = usetexfile,
    luc  = useluafile,
    lua  = useluafile,
    cld  = usecldfile,
    xml  = usexmlfile,
    [""] = usetexfile,
}

local function useanyfile(name,onlyonce)
    local s = suffixes[suffixonly(name)]
    context(function() resolvers.pushpath(name) end)
    if s then
     -- s(removesuffix(name),onlyonce)
        s(name,onlyonce) -- so, first with suffix, then without
    else
        usetexfile(name,onlyonce) -- e.g. ctx file
     -- resolvers.readfilename(name)
    end
    context(resolvers.poppath)
end

implement { name = "usetexfile",     actions = usetexfile, arguments = "string" }
implement { name = "useluafile",     actions = useluafile, arguments = "string" }
implement { name = "usecldfile",     actions = usecldfile, arguments = "string" }
implement { name = "usexmlfile",     actions = usexmlfile, arguments = "string" }

implement { name = "usetexfileonce", actions = usetexfile, arguments = { "string", true } }
implement { name = "useluafileonce", actions = useluafile, arguments = { "string", true } }
implement { name = "usecldfileonce", actions = usecldfile, arguments = { "string", true } }
implement { name = "usexmlfileonce", actions = usexmlfile, arguments = { "string", true } }

implement { name = "useanyfile",     actions = useanyfile, arguments = "string" }
implement { name = "useanyfileonce", actions = useanyfile, arguments = { "string", true } }

function jobresolvers.usefile(name,onlyonce,notext)
    local s = suffixes[suffixonly(name)]
    if s then
     -- s(removesuffix(name),onlyonce,notext)
        s(name,onlyonce,notext) -- so, first with suffix, then without
    end
end

-- document structure

local textlevel = 0 -- inaccessible for user, we need to define counter textlevel at the tex end

local function dummyfunction() end

local function startstoperror()
    report("invalid \\%s%s ... \\%s%s structure",elements.start,v_text,elements.stop,v_text)
    startstoperror = dummyfunction
end

local stopped

local function starttext()
    if textlevel == 0 then
        if trace_jobfiles then
            report_jobfiles("starting text")
        end
        context.dostarttext()
    end
    textlevel = textlevel + 1
    texsetcount("global","textlevel",textlevel)
end

local function stoptext()
    if not stopped then
        if textlevel == 0 then
            startstoperror()
        elseif textlevel > 0 then
            textlevel = textlevel - 1
        end
        texsetcount("global","textlevel",textlevel)
        if textlevel <= 0 then
            if trace_jobfiles then
                report_jobfiles("stopping text")
            end
            context.dostoptext()
            stopped = true
        end
    end
end

implement { name = "starttext", actions = starttext }
implement { name = "stoptext",  actions = stoptext  }

implement {
    name      = "forcequitjob",
    arguments = "string",
    actions   = function(reason)
        if reason then
            report("forcing quit: %s",reason)
        else
            report("forcing quit")
        end
        context.batchmode()
        while textlevel >= 0 do
            context.stoptext()
        end
    end
}

implement {
    name    = "forceendjob",
    actions = function()
        report([[don't use \end to finish a document]])
        context.stoptext()
    end
}

implement {
    name    = "autostarttext",
    actions = function()
        if textlevel == 0 then
            report([[auto \starttext ... \stoptext]])
        end
        context.starttext()
    end
}

implement {
    name    = "autostoptext",
    actions = stoptext
}

-- project structure

implement {
    name      = "processfilemany",
    arguments = { "string", false },
    actions   = useanyfile
}

implement {
    name      = "processfileonce",
    arguments = { "string", true },
    actions   = useanyfile
}

implement {
    name      = "processfilenone",
    arguments = "string",
    actions   = dummyfunction,
}

local tree               = { type = "text", name = "", branches = { } }
local treestack          = { }
local top                = tree.branches
local root               = tree

local project_stack      = { }
local product_stack      = { }
local component_stack    = { }
local environment_stack  = { }

local stacks = {
    [v_project    ] = project_stack,
    [v_product    ] = product_stack,
    [v_component  ] = component_stack,
    [v_environment] = environment_stack,
}

--

local function pushtree(what,name)
    local t = { }
    top[#top+1] = { type = what, name = name, branches = t }
    insert(treestack,top)
    top = t
end

local function poptree()
    top = remove(treestack)
 -- inspect(top)
end

do

    local function log_tree(report,top,depth)
        report("%s%s: %s",depth,top.type,top.name)
        local branches = top.branches
        if #branches > 0 then
            depth = depth .. "  "
            for i=1,#branches do
                log_tree(report,branches[i],depth)
            end
        end
    end

    logs.registerfinalactions(function()
        root.name = environment.jobname
        --
        logs.startfilelogging(report,"used job files")
        log_tree(report,root,"")
        logs.stopfilelogging()
        --
        if nofmissing > 0 and logs.loggingerrors() then
            logs.starterrorlogging(report,"missing files")
            for kind, list in sortedhash(missing) do
                for name in sortedhash(list) do
                    report("%w%s  %s",6,kind,name)
                end
            end
            logs.stoperrorlogging()
        end
    end)

end

local jobstructure      = job.structure or { }
job.structure           = jobstructure
jobstructure.collected  = jobstructure.collected or { }
jobstructure.tobesaved  = root
jobstructure.components = { }

local function initialize()
    local function collect(root,result)
        local branches = root.branches
        if branches then
            for i=1,#branches do
                local branch = branches[i]
                if branch.type == "component" then
                    result[#result+1] = branch.name
                end
                collect(branch,result)
            end
        end
        return result
    end
    jobstructure.components = collect(jobstructure.collected,{})
end

job.register('job.structure.collected',root,initialize)

-- component: small unit, either or not components itself
-- product  : combination of components

local ctx_processfilemany = context.processfilemany
local ctx_processfileonce = context.processfileonce
local ctx_processfilenone = context.processfilenone

-- we need a plug in the nested loaded, push pop pseudo current dir

local function processfilecommon(name,action)
    -- experiment, might go away
--     if not hasscheme(name) then
--         local path = dirname(name)
--         if path ~= "" then
--             registerextrapath(path)
--             report_jobfiles("adding search path %a",path)
--         end
--     end
    -- till here
    action(name)
end

local function processfilemany(name) processfilecommon(name,ctx_processfilemany) end
local function processfileonce(name) processfilecommon(name,ctx_processfileonce) end
local function processfilenone(name) processfilecommon(name,ctx_processfilenone) end

local processors = utilities.storage.allocate {
 -- [v_outer] = {
 --     [v_text]        = { "many", processfilemany },
 --     [v_project]     = { "once", processfileonce },
 --     [v_environment] = { "once", processfileonce },
 --     [v_product]     = { "once", processfileonce },
 --     [v_component]   = { "many", processfilemany },
 -- },
    [v_text] = {
        [v_text]        = { "many", processfilemany },
        [v_project]     = { "once", processfileonce }, -- dubious
        [v_environment] = { "once", processfileonce },
        [v_product]     = { "many", processfilemany }, -- dubious
        [v_component]   = { "many", processfilemany },
    },
    [v_project] = {
        [v_text]        = { "many", processfilemany },
        [v_project]     = { "none", processfilenone },
        [v_environment] = { "once", processfileonce },
        [v_product]     = { "none", processfilenone },
        [v_component]   = { "none", processfilenone },
    },
    [v_environment] = {
        [v_text]        = { "many", processfilemany },
        [v_project]     = { "none", processfilenone },
        [v_environment] = { "once", processfileonce },
        [v_product]     = { "none", processfilenone },
        [v_component]   = { "none", processfilenone },
    },
    [v_product] = {
        [v_text]        = { "many", processfilemany },
        [v_project]     = { "once", processfileonce },
        [v_environment] = { "once", processfileonce },
        [v_product]     = { "many", processfilemany },
        [v_component]   = { "many", processfilemany },
    },
    [v_component] = {
        [v_text]        = { "many", processfilemany },
        [v_project]     = { "once", processfileonce },
        [v_environment] = { "once", processfileonce },
        [v_product]     = { "none", processfilenone },
        [v_component]   = { "many", processfilemany },
    }
}

local start = {
    [v_text]        = nil,
    [v_project]     = nil,
    [v_environment] = context.startreadingfile,
    [v_product]     = context.starttext,
    [v_component]   = context.starttext,
}

local stop = {
    [v_text]        = nil,
    [v_project]     = nil,
    [v_environment] = context.stopreadingfile,
    [v_product]     = context.stoptext,
    [v_component]   = context.stoptext,
}

jobresolvers.processors = processors

local function topofstack(what)
    local stack = stacks[what]
    return stack and stack[#stack] or environment.jobname
end

local function productcomponent() -- only when in product
    local product = product_stack[#product_stack]
    if product and product ~= "" then
        local component = component_stack[1]
        if component and component ~= "" then
            return component
        end
    end
end

local function justacomponent()
    local product = product_stack[#product_stack]
    if not product or product == "" then
        local component = component_stack[1]
        if component and component ~= "" then
            return component
        end
    end
end

jobresolvers.productcomponent = productcomponent
jobresolvers.justacomponent   = justacomponent

function jobresolvers.currentproject    () return topofstack(v_project    ) end
function jobresolvers.currentproduct    () return topofstack(v_product    ) end
function jobresolvers.currentcomponent  () return topofstack(v_component  ) end
function jobresolvers.currentenvironment() return topofstack(v_environment) end

local done     = { }
local tolerant = false -- too messy, mkii user with the wrong structure should adapt

local function process(what,name)
    local depth = #typestack
    local process
    --
    name = resolveprefix(name)
    --
--  if not tolerant then
        -- okay, would be best but not compatible with mkii
        process = processors[currenttype][what]
--  elseif depth == 0 then
--      -- could be a component, product or (brr) project
--      if trace_jobfiles then
--          report_jobfiles("%s : %s > %s (case 1)",depth,currenttype,v_outer)
--      end
--      process = processors[v_outer][what]
--  elseif depth == 1 and typestack[1] == v_text then
--      -- we're still not doing a component or product
--      if trace_jobfiles then
--          report_jobfiles("%s : %s > %s (case 2)",depth,currenttype,v_outer)
--      end
--      process = processors[v_outer][what]
--  else
--      process = processors[currenttype][what]
--  end
    if process then
        local method = process[1]
        if method == "none" then
            if trace_jobfiles then
                report_jobfiles("%s : %s : %s %s %a in %s %a",depth,method,"ignoring",what,name,currenttype,topofstack(currenttype))
            end
        elseif method == "once" and done[name] then
            if trace_jobfiles then
                report_jobfiles("%s : %s : %s %s %a in %s %a",depth,method,"skipping",what,name,currenttype,topofstack(currenttype))
            end
        else
            -- keep in mind that we also handle "once" at the file level
            -- so there is a double catch
            done[name] = true
            local before = start[what]
            local after  = stop [what]
            if trace_jobfiles then
                report_jobfiles("%s : %s : %s %s %a in %s %a",depth,method,"processing",what,name,currenttype,topofstack(currenttype))
            end
            if before then
                before()
            end
            process[2](name)
            if after then
                after()
            end
        end
    else
        if trace_jobfiles then
            report_jobfiles("%s : %s : %s %s %a in %s %a",depth,"none","ignoring",what,name,currenttype,topofstack(currenttype))
        end
    end
end

implement { name = "useproject",     actions = function(name) process(v_project,    name) end, arguments = "string" }
implement { name = "useenvironment", actions = function(name) process(v_environment,name) end, arguments = "string" }
implement { name = "useproduct",     actions = function(name) process(v_product,    name) end, arguments = "string" } -- will be overloaded
implement { name = "usecomponent",   actions = function(name) process(v_component,  name) end, arguments = "string" }

-- todo: setsystemmode to currenttype
-- todo: make start/stop commands at the tex end

local start = {
    [v_project]     = context.startprojectindeed,
    [v_product]     = context.startproductindeed,
    [v_component]   = context.startcomponentindeed,
    [v_environment] = context.startenvironmentindeed,
}

local stop = {
    [v_project]     = context.stopprojectindeed,
    [v_product]     = context.stopproductindeed,
    [v_component]   = context.stopcomponentindeed,
    [v_environment] = context.stopenvironmentindeed,
}

local function gotonextlevel(what,name) -- todo: something with suffix name
    insert(stacks[what],name)
    insert(typestack,currenttype)
    currenttype = what
    pushtree(what,name)
    if start[what] then
        start[what]()
    end
end

local function gotopreviouslevel(what)
    if stop[what] then
        stop[what]()
    end
    poptree()
    currenttype = remove(typestack) or v_text
    remove(stacks[what]) -- not currenttype ... weak recovery
 -- context.endinput() -- does not work
    context.signalendofinput(what)
end

local function autoname(name)
    if name == "*" then
     -- name = nameonly(toppath() or name)
        name = nameonly(currentfile() or name)
    end
    return name
end

implement { name = "startproject",       actions = function(name) gotonextlevel(v_project,    autoname(name)) end, arguments = "string" }
implement { name = "startproduct",       actions = function(name) gotonextlevel(v_product,    autoname(name)) end, arguments = "string" }
implement { name = "startcomponent",     actions = function(name) gotonextlevel(v_component,  autoname(name)) end, arguments = "string" }
implement { name = "startenvironment",   actions = function(name) gotonextlevel(v_environment,autoname(name)) end, arguments = "string" }

implement { name = "stopproject",        actions = function() gotopreviouslevel(v_project    ) end }
implement { name = "stopproduct",        actions = function() gotopreviouslevel(v_product    ) end }
implement { name = "stopcomponent",      actions = function() gotopreviouslevel(v_component  ) end }
implement { name = "stopenvironment",    actions = function() gotopreviouslevel(v_environment) end }

implement { name = "currentproject",     actions = function() context(topofstack(v_project    )) end }
implement { name = "currentproduct",     actions = function() context(topofstack(v_product    )) end }
implement { name = "currentcomponent",   actions = function() context(topofstack(v_component  )) end }
implement { name = "currentenvironment", actions = function() context(topofstack(v_environment)) end }

-- -- -- this will move -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
--
--  <?xml version='1.0' standalone='yes'?>
--  <exa:variables xmlns:exa='htpp://www.pragma-ade.com/schemas/exa-variables.rng'>
--      <exa:variable label='mode:pragma'>nee</exa:variable>
--      <exa:variable label='mode:variant'>standaard</exa:variable>
--  </exa:variables>
--
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

local report_examodes = logs.reporter("system","examodes")

local function convertexamodes(str)
    local x = xml.convert(str)
    for e in xml.collected(x,"exa:variable") do
        local label = e.at and e.at.label
        if label and label ~= "" then
            local data = xml.text(e)
            local mode = match(label,"^mode:(.+)$")
            if mode then
                context.enablemode { formatters["%s:%s"](mode,data) }
            end
            context.setvariable("exa:variables",label,(gsub(data,"([{}])","\\%1")))
        end
    end
end

function environment.loadexamodes(filename)
    if not filename or filename == "" then
        filename = removesuffix(tex.jobname)
    end
    filename = resolvers.findfile(addsuffix(filename,'ctm')) or ""
    if filename ~= "" then
        report_examodes("loading %a",filename) -- todo: message system
        convertexamodes(io.loaddata(filename))
    else
        report_examodes("no mode file %a",filename) -- todo: message system
    end
end

implement {
    name      = "loadexamodes",
    actions   = environment.loadexamodes,
    arguments = "string"
}

-- changed in mtx-context
-- code moved from luat-ini

-- todo: locals when mtx-context is changed

document = document or {
    arguments = allocate(),
    files     = allocate(),
    variables = allocate(), -- for templates
    options   = {
        commandline = {
            environments = allocate(),
            modules      = allocate(),
            modes        = allocate(),
        },
        ctxfile = {
            environments = allocate(),
            modules      = allocate(),
            modes        = allocate(),
        },
    },
    functions = table.setmetatablenewindex(function(t,k,v)
        if rawget(t,k) then
            report_functions("overloading document function %a",k)
        end
        rawset(t,k,v)
        return v
    end),
}

function document.setargument(key,value)
    document.arguments[key] = value
end

function document.setdefaultargument(key,default)
    local v = document.arguments[key]
    if v == nil or v == "" then
        document.arguments[key] = default
    end
end

function document.setfilename(i,name)
    if name then
        document.files[tonumber(i)] = name
    else
        document.files[#document.files+1] = tostring(i)
    end
end

function document.getargument(key,default)
    local v = document.arguments[key]
    if type(v) == "boolean" then
        v = (v and "yes") or "no"
        document.arguments[key] = v
    end
    return v or default or ""
end

function document.getfilename(i)
    return document.files[tonumber(i)] or ""
end

implement {
    name      = "setdocumentargument",
    actions   = document.setargument,
    arguments = "2 strings"
}

implement {
    name      = "setdocumentdefaultargument",
    actions   = document.setdefaultargument,
    arguments = "2 strings"
}

implement {
    name      = "setdocumentfilename",
    actions   = document.setfilename,
    arguments = { "integer", "string" }
}

implement {
    name      = "getdocumentargument",
    actions   = { document.getargument, context },
    arguments = "2 strings"
}

implement {
    name      = "getdocumentfilename",
    actions   = { document.getfilename, context },
    arguments = "integer"
}

function document.setcommandline() -- has to happen at the tex end in order to expand

    -- the document[arguments|files] tables are copies

    local arguments = document.arguments
    local files     = document.files
    local options   = document.options

    for k, v in next, environment.arguments do
        k = gsub(k,"^c:","") -- already done, but better be safe than sorry
        if arguments[k] == nil then
            arguments[k] = v
        end
    end

    -- in the new mtx=context approach we always pass a stub file so we need to
    -- to trick the files table which actually only has one entry in a tex job

    if arguments.timing then
        context.usemodule { "timing" }
    end

    if arguments.usage then
       trackers.enable("system.usage")
    end

    if arguments.batchmode then
        context.batchmode(false)
    end

    if arguments.nonstopmode then
        context.nonstopmode(false)
    end

    if arguments.nostatistics then
        directives.enable("system.nostatistics")
    end

    if arguments.paranoid then
        context.setvalue("maxreadlevel",1)
    end

    if validstring(arguments.path)  then
        context.usepath { arguments.path }
    end

    if arguments.export then
        context.setupbackend { export = v_yes }
    end

    local inputfile = validstring(arguments.input)

    if inputfile and dirname(inputfile) == "." and lfs.isfile(inputfile) then
        -- nicer in checks
        inputfile = basename(inputfile)
    end

    local forcedruns = arguments.forcedruns
    local kindofrun  = arguments.kindofrun
    local currentrun = arguments.currentrun
    local maxnofruns = arguments.maxnofruns or arguments.runs

 -- context.setupsystem {
 --     [constants.directory] = validstring(arguments.setuppath),
 --     [constants.inputfile] = inputfile,
 --     [constants.file]      = validstring(arguments.result),
 --     [constants.random]    = validstring(arguments.randomseed),
 --     -- old:
 --     [constants.n]         = validstring(kindofrun),
 --     [constants.m]         = validstring(currentrun),
 -- }

    context.setupsystem {
        directory = validstring(arguments.setuppath),
        inputfile = inputfile,
        file      = validstring(arguments.result),
        random    = validstring(arguments.randomseed),
        -- old:
        n         = validstring(kindofrun),
        m         = validstring(currentrun),
    }

    forcedruns = tonumber(forcedruns) or 0
    kindofrun  = tonumber(kindofrun)  or 0
    maxnofruns = tonumber(maxnofruns) or 0
    currentrun = tonumber(currentrun) or 0

    local prerollrun = forcedruns > 0 and currentrun > 0 and currentrun < forcedruns

    environment.forcedruns = forcedruns
    environment.kindofrun  = kindofrun
    environment.maxnofruns = maxnofruns
    environment.currentrun = currentrun
    environment.prerollrun = prerollrun

    context.setconditional("prerollrun",prerollrun)

    if validstring(arguments.arguments) then
        context.setupenv { arguments.arguments }
    end

    if arguments.once then
        directives.enable("system.runonce")
    end

    if arguments.noarrange then
        context.setuparranging { variables.disable }
    end

    --

    local commandline  = options.commandline

    commandline.environments = table.append(commandline.environments,settings_to_array(validstring(arguments.environment)))
    commandline.modules      = table.append(commandline.modules,     settings_to_array(validstring(arguments.usemodule)))
    commandline.modes        = table.append(commandline.modes,       settings_to_array(validstring(arguments.mode)))

    --

    if #files == 0 then
        local list = settings_to_array(validstring(arguments.files))
        if list and #list > 0 then
            files = list
        end
    end

    if #files == 0 then
        files = { validstring(arguments.input) }
    end

    --

    document.arguments = arguments
    document.files     = files

end

-- commandline wins over ctxfile

local function apply(list,action)
    if list then
        for i=1,#list do
            action { list[i] }
        end
    end
end

function document.setmodes() -- was setup: *runtime:modes
    apply(document.options.ctxfile    .modes,context.enablemode)
    apply(document.options.commandline.modes,context.enablemode)
end

function document.setmodules() -- was setup: *runtime:modules
    apply(document.options.ctxfile    .modules,context.usemodule)
    apply(document.options.commandline.modules,context.usemodule)
end

function document.setenvironments() -- was setup: *runtime:environments
    apply(document.options.ctxfile    .environments,context.environment)
    apply(document.options.commandline.environments,context.environment)
end

function document.setfilenames()
    local initialize = environment.initializefilenames
    if initialize then
        initialize()
    else
        -- fatal error
    end
end

implement { name = "setdocumentcommandline",  actions = document.setcommandline,  onlyonce = true }
implement { name = "setdocumentmodes",        actions = document.setmodes,        onlyonce = true }
implement { name = "setdocumentmodules",      actions = document.setmodules,      onlyonce = true }
implement { name = "setdocumentenvironments", actions = document.setenvironments, onlyonce = true }
implement { name = "setdocumentfilenames",    actions = document.setfilenames,    onlyonce = true }

do

    logs.registerfinalactions(function()
        local foundintrees = resolvers.foundintrees()
        if #foundintrees > 0 then
            logs.startfilelogging(report,"used files")
            for i=1,#foundintrees do
                report("%4i: % T",i,foundintrees[i])
            end
            logs.stopfilelogging()
        end
    end)

    logs.registerfinalactions(function()
        local files = document.files -- or environment.files
        local arguments = document.arguments -- or environment.arguments
        --
        logs.startfilelogging(report,"commandline options")
        if arguments and next(arguments) then
            for argument, value in sortedhash(arguments) do
                report("%s=%A",argument,value)
            end
        else
            report("no arguments")
        end
        logs.stopfilelogging()
        --
        logs.startfilelogging(report,"commandline files")
        if files and #files > 0 then
            for i=1,#files do
                report("% 4i: %s",i,files[i])
            end
        else
            report("no files")
        end
        logs.stopfilelogging()
    end)

end

if environment.initex then

    logs.registerfinalactions(function()
        local startfilelogging = logs.startfilelogging
        local stopfilelogging  = logs.stopfilelogging
        startfilelogging(report,"stored tables")
        for k,v in sortedhash(storage.data) do
            report("%03i %s",k,v[1])
        end
        stopfilelogging()
        startfilelogging(report,"stored modules")
        for k,v in sortedhash(lua.bytedata) do
            report("%03i %s %s",k,v.name)
        end
        stopfilelogging()
        startfilelogging(report,"stored attributes")
        for k,v in sortedhash(attributes.names) do
            report("%03i %s",k,v)
        end
        stopfilelogging()
        startfilelogging(report,"stored catcodetables")
        for k,v in sortedhash(catcodes.names) do
            report("%03i % t",k,v)
        end
        stopfilelogging()
        startfilelogging(report,"stored corenamespaces")
        for k,v in sortedhash(interfaces.corenamespaces) do
            report("%03i %s",k,v)
        end
        stopfilelogging()
    end)

end

implement {
    name      = "doifelsecontinuewithfile",
    arguments = "string",
    actions   = function(inpname,basetoo)
        local inpnamefull = addsuffix(inpname,"tex")
        local inpfilefull = addsuffix(environment.inputfilename,"tex")
        local continue = inpnamefull == inpfilefull
     -- if basetoo and not continue then
        if not continue then
            continue = inpnamefull == basename(inpfilefull)
        end
        if continue then
            report("continuing input file %a",inpname)
        end
        ctx_doifelse(continue)
    end
}
