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

-- In fact all writes could go through lua and we could write the console and
-- terminal handler in lua then. Ok, maybe it's slower then, so a no-go.
--
-- This is the version for mtxrun. The alternative functions for the TeX engines
-- have been separated. In order to keep in sync we use tex specific witer names.
--
-- Todo: some cleanup (less local needed).

local next, type, select, print = next, type, select, print
local format, gmatch, find = string.format, string.gmatch, string.find
local concat, insert, remove = table.concat, table.insert, table.remove
local topattern = string.topattern
local utfchar = utf.char
local datetime = os.date
local sleep = os.sleep
local openfile = io.open

local write_nl = print
local write    = io.write

local setmetatableindex = table.setmetatableindex
local formatters        = string.formatters
local settings_to_hash  = utilities.parsers.settings_to_hash
local sortedkeys        = table.sortedkeys

local variant = "default"
----- variant = "ansi"

logs       = logs or { }
local logs = logs

local moreinfo = [[
More information about ConTeXt and the tools that come with it can be found at:
]] .. "\n" .. [[
maillist : ntg-context@ntg.nl / http://www.ntg.nl/mailman/listinfo/ntg-context
webpage  : http://www.pragma-ade.nl / http://tex.aanhet.net
wiki     : http://contextgarden.net
]]

formatters.add (
    formatters, "unichr",
    [["U+" .. format("%%05X",%s) .. " (" .. utfchar(%s) .. ")"]]
)

formatters.add (
    formatters, "chruni",
    [[utfchar(%s) .. " (U+" .. format("%%05X",%s) .. ")"]]
)

-- basic loggers

local function ignore() end

setmetatableindex(logs, function(t,k) t[k] = ignore ; return ignore end)

local report, subreport, status, settarget, setformats, settranslations

local direct, subdirect, writer, pushtarget, poptarget, setlogfile, settimedlog, setprocessor, setformatters, newline

-- we use formatters but best check for % then because for simple messages but
-- we don't want this overhead for single messages (not that there are that
-- many; we could have a special weak table)

local function ansisupported(specification)
    if specification ~= "ansi" and specification ~= "ansilog" then
        return false
    elseif os and os.enableansi then
        return os.enableansi()
    else
        return false
    end
end

do

    local report_yes, subreport_yes, status_yes
    local report_nop, subreport_nop, status_nop

    local variants = {
        default = {
            formats = {
                report_yes    = formatters["%-15s | %s"],
                report_nop    = formatters["%-15s |"],
                subreport_yes = formatters["%-15s | %s | %s"],
                subreport_nop = formatters["%-15s | %s |"],
                status_yes    = formatters["%-15s : %s\n"],
                status_nop    = formatters["%-15s :\n"],
            },
        },
        ansi = {
            formats = {
                report_yes    = formatters["%-15s | %s"],
                report_nop    = formatters["%-15s |"],
                subreport_yes = formatters["%-15s | %s | %s"],
                subreport_nop = formatters["%-15s | %s |"],
                status_yes    = formatters["%-15s : %s\n"],
                status_nop    = formatters["%-15s :\n"],
            },
        },
    }

    logs.flush = ignore

    writer = function(s)
        write_nl(s)
    end

    newline = function()
        write_nl("\n")
    end

    report = function(a,b,c,...)
        if c then
            write_nl(report_yes(a,formatters[b](c,...)))
        elseif b then
            write_nl(report_yes(a,b))
        elseif a then
            write_nl(report_nop(a))
        else
            write_nl("")
        end
    end

    subreport = function(a,sub,b,c,...)
        if c then
            write_nl(subreport_yes(a,sub,formatters[b](c,...)))
        elseif b then
            write_nl(subreport_yes(a,sub,b))
        elseif a then
            write_nl(subreport_nop(a,sub))
        else
            write_nl("")
        end
    end

    status = function(a,b,c,...) -- not to be used in lua anyway
        if c then
            write_nl(status_yes(a,formatters[b](c,...)))
        elseif b then
            write_nl(status_yes(a,b)) -- b can have %'s
        elseif a then
            write_nl(status_nop(a))
        else
            write_nl("\n")
        end
    end

    direct          = ignore
    subdirect       = ignore

    settarget       = ignore
    pushtarget      = ignore
    poptarget       = ignore
    setformats      = ignore
    settranslations = ignore

    setprocessor = function(f)
        local writeline = write_nl
        write_nl = function(s)
            writeline(f(s))
        end
    end

    setformatters = function(specification)
        local f = nil
        local d = variants.default
        if specification then
            if type(specification) == "table" then
                f = specification.formats or specification
            else
                if not ansisupported(specification) then
                    specification = "default"
                end
                local v = variants[specification]
                if v then
                    f = v.formats
                end
            end
        end
        if f then
            d = d.formats
        else
            f = d.formats
            d = f
        end
        setmetatableindex(f,d)
        report_yes    = f.report_yes
        report_nop    = f.report_nop
        subreport_yes = f.subreport_yes
        subreport_nop = f.subreport_nop
        status_yes    = f.status_yes
        status_nop    = f.status_nop
    end

    setformatters(variant)

    setlogfile = function(name,keepopen)
        if name and name ~= "" then
            local localtime = os.localtime
            local writeline = write_nl
            if keepopen then
                local f = io.open(name,"ab")
                write_nl = function(s)
                    writeline(s)
                    f:write(localtime()," | ",s,"\n")
                end
            else
                write_nl = function(s)
                    writeline(s)
                    local f = io.open(name,"ab")
                    f:write(localtime()," | ",s,"\n")
                    f:close()
                end
            end
        end
        setlogfile = ignore
    end

    settimedlog = function()
        local localtime = os.localtime
        local writeline = write_nl
        write_nl = function(s)
            writeline(localtime() .. " | " .. s)
        end
        settimedlog = ignore
    end

end

logs.report          = report
logs.subreport       = subreport
logs.status          = status
logs.settarget       = settarget
logs.pushtarget      = pushtarget
logs.poptarget       = poptarget
logs.setformats      = setformats
logs.settranslations = settranslations

logs.setlogfile      = setlogfile
logs.settimedlog     = settimedlog
logs.setprocessor    = setprocessor
logs.setformatters   = setformatters

logs.direct          = direct
logs.subdirect       = subdirect
logs.writer          = writer
logs.newline         = newline

-- installer

-- todo: renew (un) locks when a new one is added and wildcard

local data   = { }
local states = nil
local force  = false

function logs.reporter(category,subcategory)
    local logger = data[category]
    if not logger then
        local state = states == true
        if not state and type(states) == "table" then
            for c, _ in next, states do
                if find(category,c) then
                    state = true
                    break
                end
            end
        end
        logger = {
            reporters = { },
            state     = state,
        }
        data[category] = logger
    end
    local reporter = logger.reporters[subcategory or "default"]
    if not reporter then
        if subcategory then
            reporter = function(...)
                if force or not logger.state then
                    subreport(category,subcategory,...)
                end
            end
            logger.reporters[subcategory] = reporter
        else
            local tag = category
            reporter = function(...)
                if force or not logger.state then
                    report(category,...)
                end
            end
            logger.reporters.default = reporter
        end
    end
    return reporter
end

logs.new = logs.reporter -- for old times sake

-- context specicific: this ends up in the macro stream

local ctxreport = logs.writer

function logs.setmessenger(m)
    ctxreport = m
end

function logs.messenger(category,subcategory)
    -- we need to avoid catcode mess (todo: fast context)
    if subcategory then
        return function(...)
            ctxreport(subdirect(category,subcategory,...))
        end
    else
        return function(...)
            ctxreport(direct(category,...))
        end
    end
end

-- so far

local function setblocked(category,value) -- v.state == value == true : disable
    if category == true or category == "all" then
        -- lock all
        category, value = "*", true
    elseif category == false then
        -- unlock all
        category, value = "*", false
    elseif value == nil then
        -- lock selective
        value = true
    end
    if category == "*" then
        states = value
        for k, v in next, data do
            v.state = value
        end
    else
        alllocked = false
        states = settings_to_hash(category,type(states)=="table" and states or nil)
        for c in next, states do
            local v = data[c]
            if v then
                v.state = value
            else
                local p = topattern(c,true,true)
                for k, v in next, data do
                    if find(k,p) then
                        v.state = value
                    end
                end
            end
        end
    end
end

function logs.disable(category,value)
    setblocked(category,value == nil and true or value)
end

function logs.enable(category)
    setblocked(category,false)
end

function logs.categories()
    return sortedkeys(data)
end

function logs.show()
    local n, c, s, max = 0, 0, 0, 0
    for category, v in table.sortedpairs(data) do
        n = n + 1
        local state = v.state
        local reporters = v.reporters
        local nc = #category
        if nc > c then
            c = nc
        end
        for subcategory, _ in next, reporters do
            local ns = #subcategory
            if ns > c then
                s = ns
            end
            local m = nc + ns
            if m > max then
                max = m
            end
        end
        local subcategories = concat(sortedkeys(reporters),", ")
        if state == true then
            state = "disabled"
        elseif state == false then
            state = "enabled"
        else
            state = "unknown"
        end
        -- no new here
        report("logging","category %a, subcategories %a, state %a",category,subcategories,state)
    end
    report("logging","categories: %s, max category: %s, max subcategory: %s, max combined: %s",n,c,s,max)
end

local delayed_reporters = { }

setmetatableindex(delayed_reporters,function(t,k)
    local v = logs.reporter(k.name)
    t[k] = v
    return v
end)

function utilities.setters.report(setter,...)
    delayed_reporters[setter](...)
end

directives.register("logs.blocked", function(v)
    setblocked(v,true)
end)

directives.register("logs.target", function(v)
    settarget(v)
end)

-- we don't have show_open and show_close callbacks yet

----- report_files = logs.reporter("files")
local nesting      = 0
local verbose      = false
local hasscheme    = url.hasscheme

-- there may be scripts out there using this:

local simple = logs.reporter("comment")

logs.simple     = simple
logs.simpleline = simple

-- obsolete

logs.setprogram   = ignore -- obsolete
logs.extendbanner = ignore -- obsolete
logs.reportlines  = ignore -- obsolete
logs.reportbanner = ignore -- obsolete
logs.reportline   = ignore -- obsolete
logs.simplelines  = ignore -- obsolete
logs.help         = ignore -- obsolete

-- applications

local Carg, C, lpegmatch = lpeg.Carg, lpeg.C, lpeg.match
local p_newline = lpeg.patterns.newline

local linewise = (
    Carg(1) * C((1-p_newline)^1) / function(t,s) t.report(s) end
  + Carg(1) * p_newline^2        / function(t)   t.report()  end
  + p_newline
)^1

local function reportlines(t,str)
    if str then
        lpegmatch(linewise,str,1,t)
    end
end

local function reportbanner(t)
    local banner = t.banner
    if banner then
        t.report(banner)
        t.report()
    end
end

local function reportversion(t)
    local banner = t.banner
    if banner then
        t.report(banner)
    end
end

local function reporthelp(t,...)
    local helpinfo = t.helpinfo
    if type(helpinfo) == "string" then
        reportlines(t,helpinfo)
    elseif type(helpinfo) == "table" then
        for i=1,select("#",...) do
            reportlines(t,t.helpinfo[select(i,...)])
            if i < n then
                t.report()
            end
        end
    end
end

local function reportinfo(t)
    t.report()
    reportlines(t,t.moreinfo)
end

local function reportexport(t,method)
    report(t.helpinfo)
end

local reporters = {
    lines   = reportlines, -- not to be overloaded
    banner  = reportbanner,
    version = reportversion,
    help    = reporthelp,
    info    = reportinfo,
    export  = reportexport,
}

local exporters = {
    -- empty
}

logs.reporters = reporters
logs.exporters = exporters

function logs.application(t)
    --
    local arguments = environment and environment.arguments
    if arguments then
        local ansi = arguments.ansi or arguments.ansilog
        if ansi then
            logs.setformatters(arguments.ansi and "ansi" or "ansilog")
        end
    end
    --
    t.name     = t.name   or "unknown"
    t.banner   = t.banner
    t.moreinfo = moreinfo
    t.report   = logs.reporter(t.name)
    t.help     = function(...)
        reporters.banner(t)
        reporters.help(t,...)
        reporters.info(t)
    end
    t.export   = function(...)
        reporters.export(t,...)
    end
    t.identify = function()
        reporters.banner(t)
    end
    t.version  = function()
        reporters.version(t)
    end
    return t
end

-- somewhat special .. will be redone (already a better solution in place in lmx)

-- logging to a file

-- local syslogname = "oeps.xxx"
--
-- for i=1,10 do
--     logs.system(syslogname,"context","test","fonts","font %s recached due to newer version (%s)","blabla","123")
-- end

local f_syslog = formatters["%s %s => %s => %s => %s\r"]

function logs.system(whereto,process,jobname,category,fmt,arg,...)
    local message = f_syslog(datetime("%d/%m/%y %H:%m:%S"),process,jobname,category,arg == nil and fmt or format(fmt,arg,...))
    for i=1,10 do
        local f = openfile(whereto,"a") -- we can consider keeping the file open
        if f then
            f:write(message)
            f:close()
            break
        else
            sleep(0.1)
        end
    end
end

local report_system = logs.reporter("system","logs")

if utilities then
    utilities.report = report_system
end

-- this is somewhat slower but prevents out-of-order messages when print is mixed
-- with texio.write

-- io.stdout:setvbuf('no')
-- io.stderr:setvbuf('no')

-- windows: > nul  2>&1
-- unix   : > null 2>&1

if package.helpers.report then
    package.helpers.report = logs.reporter("package loader") -- when used outside mtxrun
end