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

-- This is an experiment: generating context code at the lua end. After all
-- it is surprisingly simple to implement due to metatables. I was wondering
-- if there was a more natural way to deal with commands at the lua end.
-- Of course it's a bit slower but often more readable when mixed with lua
-- code. It can also be handy when generating documents from databases or
-- when constructing large tables or so.
--
-- Todo: optional checking against interface
-- Todo: coroutine trickery
-- Todo: maybe use txtcatcodes

-- tflush needs checking ... sort of weird that it's not a table

-- __flushlines is an experiment and rather ugly so it will go away

local tex = tex

context       = context or { }
local context = context

local format, find, gmatch, splitlines = string.format, string.find, string.gmatch, string.splitlines
local next, type, tostring, setmetatable = next, type, tostring, setmetatable
local insert, remove, concat = table.insert, table.remove, table.concat
local lpegmatch = lpeg.match

local texsprint         = tex.sprint
local textprint         = tex.tprint
local texprint          = tex.print
local texiowrite        = texio.write
local texcount          = tex.count

local isnode            = node.is_node -- after 0.65 just node.type
local writenode         = node.write
local copynodelist      = node.copy_list

local ctxcatcodes       = tex.ctxcatcodes
local prtcatcodes       = tex.prtcatcodes
local texcatcodes       = tex.texcatcodes
local txtcatcodes       = tex.txtcatcodes
local vrbcatcodes       = tex.vrbcatcodes
local xmlcatcodes       = tex.xmlcatcodes

local flush             = texsprint

local report_context    = logs.reporter("cld","tex")
local report_cld        = logs.reporter("cld","stack")

local processlines      = true -- experiments.register("context.processlines", function(v) processlines = v end)

-- for tracing it's easier to have two stacks

local _stack_f_, _n_f_ = { }, 0
local _stack_n_, _n_n_ = { }, 0

local function _store_f_(ti)
    _n_f_ = _n_f_ + 1
    _stack_f_[_n_f_] = ti
    return _n_f_
end

local function _store_n_(ti)
    _n_n_ = _n_n_ + 1
    _stack_n_[_n_n_] = ti
    return _n_n_
end

local function _flush_f_(n)
    local sn = _stack_f_[n]
    if not sn then
        report_cld("data with id %s cannot be found on stack",n)
    else
        local tn = type(sn)
        if tn == "function" then
            if not sn() and texcount["@@trialtypesetting"] == 0 then  -- @@trialtypesetting is private!
                _stack_f_[n] = nil
            else
                -- keep, beware, that way the stack can grow
            end
        else
            if texcount["@@trialtypesetting"] == 0 then  -- @@trialtypesetting is private!
                writenode(sn)
                _stack_f_[n] = nil
            else
                writenode(copynodelist(sn))
                -- keep, beware, that way the stack can grow
            end
        end
    end
end

local function _flush_n_(n)
    local sn = _stack_n_[n]
    if not sn then
        report_cld("data with id %s cannot be found on stack",n)
    elseif texcount["@@trialtypesetting"] == 0 then  -- @@trialtypesetting is private!
        writenode(sn)
        _stack_n_[n] = nil
    else
        writenode(copynodelist(sn))
        -- keep, beware, that way the stack can grow
    end
end

function context.restart()
    _stack_f_, _n_f_ = { }, 0
    _stack_n_, _n_n_ = { }, 0
end

context._stack_f_ = _stack_f_
context._store_f_ = _store_f_
context._flush_f_ = _flush_f_  _cldf_ = _flush_f_

context._stack_n_ = _stack_n_
context._store_n_ = _store_n_
context._flush_n_ = _flush_n_  _cldn_ = _flush_n_

-- Should we keep the catcodes with the function?

local catcodestack    = { }
local currentcatcodes = ctxcatcodes
local contentcatcodes = ctxcatcodes

local catcodes = {
    ctx = ctxcatcodes, ctxcatcodes = ctxcatcodes, context  = ctxcatcodes,
    prt = prtcatcodes, prtcatcodes = prtcatcodes, protect  = prtcatcodes,
    tex = texcatcodes, texcatcodes = texcatcodes, plain    = texcatcodes,
    txt = txtcatcodes, txtcatcodes = txtcatcodes, text     = txtcatcodes,
    vrb = vrbcatcodes, vrbcatcodes = vrbcatcodes, verbatim = vrbcatcodes,
    xml = xmlcatcodes, xmlcatcodes = xmlcatcodes,
}

function context.pushcatcodes(c)
    insert(catcodestack,currentcatcodes)
    currentcatcodes = (c and catcodes[c] or tonumber(c)) or currentcatcodes
    contentcatcodes = currentcatcodes
end

function context.popcatcodes()
    currentcatcodes = remove(catcodestack) or currentcatcodes
    contentcatcodes = currentcatcodes
end

function tex.fprint(...) -- goodie
    texsprint(currentcatcodes,format(...))
end

-- -- -- todo: tracing

local newline    = lpeg.patterns.newline
local space      = lpeg.patterns.spacer
local spacing    = newline * space^0
local content    = lpeg.C((1-spacing)^1)
local emptyline  = space^0 * newline^2
local endofline  = space^0 * newline * space^0
local simpleline = endofline * lpeg.P(-1)

local function n_content(s)
    flush(contentcatcodes,s)
end

local function n_endofline()
    texsprint(" \r")
end

local function n_emptyline()
    texprint("\r")
end

local function n_simpleline()
    texprint("\r")
end

function lpeg.texlinesplitter(f_content,f_endofline,f_emptyline,f_simpleline)
    local splitlines =
        simpleline / (f_simpleline or n_simpleline)
      + (
            emptyline / (f_emptyline or n_emptyline)
          + endofline / (f_endofline or n_emptyline)
          + content   / (f_content or n_content)
        )^0
    return function(str) return lpegmatch(splitlines,str) end
end

local flushlines = lpeg.texlinesplitter(n_content,n_endofline,n_emptyline,n_simpleline)

context.__flushlines = flushlines       -- maybe context.helpers.flushtexlines
context.__flush      = flush

local printlines_ctx = (
    (newline)     / function()  texprint("") end +
    (1-newline)^1 / function(s) texprint(ctxcatcodes,s) end * newline^-1
)^0

local printlines_raw = (
    (newline)     / function()  texprint("") end +
    (1-newline)^1 / function(s) texprint(s)  end * newline^-1
)^0

function context.printlines(str,raw)
    if raw then
        lpegmatch(printlines_raw,str)
    else
        lpegmatch(printlines_ctx,str)
    end
end

-- -- --

local methodhandler = resolvers.methodhandler

function context.viafile(data)
    -- this is the only way to deal with nested buffers
    -- and other catcode sensitive data
    if data and data ~= "" then
        local filename = resolvers.savers.byscheme("virtual","viafile",data)
        context.input(filename)
    end
end

-- -- --

local function writer(parent,command,first,...)
    local t = { first, ... }
    flush(currentcatcodes,command) -- todo: ctx|prt|texcatcodes
    local direct = false
    for i=1,#t do
        local ti = t[i]
        local typ = type(ti)
        if direct then
            if typ == "string" or typ == "number" then
                flush(currentcatcodes,ti)
            else -- node.write
                report_context("error: invalid use of direct in '%s', only strings and numbers can be flushed directly, not '%s'",command,typ)
            end
            direct = false
        elseif ti == nil then
            -- nothing
        elseif ti == "" then
            flush(currentcatcodes,"{}")
        elseif typ == "string" then
            if processlines and find(ti,"[\n\r]") then -- we can check for ti == "\n"
                flush(currentcatcodes,"{")
                local flushlines = parent.__flushlines or flushlines
                flushlines(ti)
                flush(currentcatcodes,"}")
            elseif currentcatcodes == contentcatcodes then
                flush(currentcatcodes,"{",ti,"}")
            else
                flush(currentcatcodes,"{")
                flush(contentcatcodes,ti)
                flush(currentcatcodes,"}")
            end
        elseif typ == "number" then
            -- numbers never have funny catcodes
            flush(currentcatcodes,"{",ti,"}")
        elseif typ == "table" then
            local tn = #ti
            if tn == 0 then
                local done = false
                for k, v in next, ti do
                    if done then
                        if v == "" then
                            flush(currentcatcodes,",",k,'=')
                        else
                            flush(currentcatcodes,",",k,'=',v)
                        end
                    else
                        if v == "" then
                            flush(currentcatcodes,"[",k,'=')
                        else
                            flush(currentcatcodes,"[",k,'=',v)
                        end
                        done = true
                    end
                end
                flush(currentcatcodes,"]")
            elseif tn == 1 then -- some 20% faster than the next loop
                local tj = ti[1]
                if type(tj) == "function" then
                    flush(currentcatcodes,"[\\cldf{",_store_f_(tj),"}]")
                else
                    flush(currentcatcodes,"[",tj,"]")
                end
            else -- is concat really faster than flushes here? probably needed anyway (print artifacts)
                for j=1,tn do
                    local tj = ti[j]
                    if type(tj) == "function" then
                        ti[j] = "\\cldf{" .. _store_f_(tj) .. "}"
                    end
                end
                flush(currentcatcodes,"[",concat(ti,","),"]")
            end
        elseif typ == "function" then
            flush(currentcatcodes,"{\\cldf{",_store_f_(ti),"}}") -- todo: ctx|prt|texcatcodes
        elseif typ == "boolean" then
            if ti then
             -- flush(currentcatcodes,"^^M")
                texprint("")
            else
                direct = true
            end
        elseif typ == "thread" then
            report_context("coroutines not supported as we cannot yield across boundaries")
        elseif isnode(ti) then -- slow
            flush(currentcatcodes,"{\\cldn{",_store_n_(ti),"}}")
        else
            report_context("error: '%s' gets a weird argument '%s'",command,tostring(ti))
        end
    end
end

local generics = { }  context.generics = generics

local function indexer(parent,k)
    local c = "\\" .. tostring(generics[k] or k)
    local f = function(first,...)
        if first == nil then
            flush(currentcatcodes,c)
        else
            return writer(parent,c,first,...)
        end
    end
    parent[k] = f
    return f
end

local function caller(parent,f,a,...)
    if not parent then
        -- so we don't need to test in the calling (slower but often no issue) (will go)
    elseif f ~= nil then
        local typ = type(f)
        if typ == "string" then
            if a then
                flush(contentcatcodes,format(f,a,...)) -- was currentcatcodes
            elseif processlines and find(f,"[\n\r]") then
                local flushlines = parent.__flushlines or flushlines
                flushlines(f)
            else
                flush(contentcatcodes,f)
            end
        elseif typ == "number" then
            if a then
                flush(currentcatcodes,f,a,...)
            else
                flush(currentcatcodes,f)
            end
        elseif typ == "function" then
            -- ignored: a ...
            flush(currentcatcodes,"{\\cldf{",_store_f_(f),"}}") -- todo: ctx|prt|texcatcodes
        elseif typ == "boolean" then
            if f then
                if a ~= nil then
                    local flushlines = parent.__flushlines or flushlines
                    flushlines(f)
                    -- ignore ... maybe some day
                else
                 -- flush(currentcatcodes,"^^M")
                    texprint("")
                end
            else
                if a ~= nil then
                    -- no command, same as context(a,...)
                    writer(parent,"",a,...)
                else
                    -- ignored
                end
            end
        elseif typ == "thread" then
            report_context("coroutines not supported as we cannot yield across boundaries")
        elseif isnode(f) then -- slow
         -- writenode(f)
            flush(currentcatcodes,"\\cldn{",_store_n_(f),"}")
        else
            report_context("error: 'context' gets a weird argument '%s'",tostring(f))
        end
    end
end

local defaultcaller = caller

setmetatable(context, { __index = indexer, __call = caller } )

-- now we tweak unprotect and protect

function context.unprotect()
    -- at the lua end
    insert(catcodestack,currentcatcodes)
    currentcatcodes = prtcatcodes
    contentcatcodes = currentcatcodes
    -- at the tex end
    flush("\\unprotect")
end

function context.protect()
    -- at the tex end
    flush("\\protect")
    -- at the lua end
    currentcatcodes = remove(catcodestack) or currentcatcodes
    contentcatcodes = currentcatcodes
end

-- logging

local trace_stack   = { }

local normalflush   = flush
local normalwriter  = writer
local currenttrace  = nil
local nofwriters    = 0
local nofflushes    = 0

statistics.register("traced context", function()
    if nofwriters > 0 or nofflushes > 0 then
        return format("writers: %s, flushes: %s, maxstack: %s",nofwriters,nofflushes,_n_f_)
    end
end)

local tracedwriter = function(parent,...)
    nofwriters = nofwriters + 1
    local t, f, n = { "w : " }, flush, 0
    flush = function(...)
        n = n + 1
        t[n] = concat({...},"",2)
        normalflush(...)
    end
    normalwriter(parent,...)
    flush = f
    currenttrace(concat(t))
end

local tracedflush = function(...)
    nofflushes = nofflushes + 1
    normalflush(...)
    local t = { ... }
    t[1] = "f : " -- replaces the catcode
    for i=2,#t do
        local ti = t[i]
        local tt = type(ti)
        if tt == "string" then
            -- ok
        elseif tt == "number" then
            -- ok
        else
            t[i] = format("<%s>",tostring(ti))
        end
    --  currenttrace(format("%02i: %s",i-1,tostring(t[i])))
    end
    currenttrace(concat(t))
end

local function pushlogger(trace)
    insert(trace_stack,currenttrace)
    currenttrace = trace
    flush, writer = tracedflush, tracedwriter
    context.__flush = flush
end

local function poplogger()
    currenttrace = remove(trace_stack)
    if not currenttrace then
        flush, writer = normalflush, normalwriter
        context.__flush = flush
    end
end

local function settracing(v)
    if v then
        pushlogger(report_context)
    else
        poplogger()
    end
end

-- todo: share flushers so that we can define in other files

trackers.register("context.trace",settracing)

context.pushlogger = pushlogger
context.poplogger  = poplogger
context.settracing = settracing

local trace_cld = false  trackers.register("context.files", function(v) trace_cld = v end)

function context.runfile(filename)
    local foundname = resolvers.findtexfile(file.addsuffix(filename,"cld")) or ""
    if foundname ~= "" then
        local ok = dofile(foundname)
        if type(ok) == "function" then
            if trace_cld then
                report_context("begin of file '%s' (function call)",foundname)
            end
            ok()
            if trace_cld then
                report_context("end of file '%s' (function call)",foundname)
            end
        elseif ok then
            report_context("file '%s' is processed and returns true",foundname)
        else
            report_context("file '%s' is processed and returns nothing",foundname)
        end
    else
        report_context("unknown file '%s'",filename)
    end
end

-- some functions

function context.direct(first,...)
    if first ~= nil then
        return writer(context,"",first,...)
    end
end

-- context.delayed (todo: lines)

local delayed = { } context.delayed = delayed -- maybe also store them

local function indexer(parent,k)
    local f = function(...)
        local a = { ... }
        return function()
            return context[k](unpack(a))
        end
    end
    parent[k] = f
    return f
end

local function caller(parent,...) -- todo: nodes
    local a = { ... }
    return function()
        return context(unpack(a))
    end
end

setmetatable(delayed, { __index = indexer, __call = caller } )

-- context.nested (todo: lines)

local nested = { } context.nested = nested

local function indexer(parent,k)
    local f = function(...)
        local t, savedflush, n = { }, flush, 0
        flush = function(c,f,s,...) -- catcodes are ignored
            n = n + 1
            t[n] = s and concat{f,s,...} or f -- optimized for #args == 1
        end
        context[k](...)
        flush = savedflush
        return concat(t)
    end
    parent[k] = f
    return f
end

local function caller(parent,...)
    local t, savedflush, n = { }, flush, 0
    flush = function(c,f,s,...) -- catcodes are ignored
        n = n + 1
        t[n] = s and concat{f,s,...} or f -- optimized for #args == 1
    end
    context(...)
    flush = savedflush
    return concat(t)
end

setmetatable(nested, { __index = indexer, __call = caller } )

-- verbatim

local verbatim = { } context.verbatim = verbatim

local function indexer(parent,k)
    local command = context[k]
    local f = function(...)
        local savedcatcodes = contentcatcodes
        contentcatcodes = vrbcatcodes
        command(...)
        contentcatcodes = savedcatcodes
    end
    parent[k] = f
    return f
end

local function caller(parent,...)
    local savedcatcodes = contentcatcodes
    contentcatcodes = vrbcatcodes
    defaultcaller(parent,...)
    contentcatcodes = savedcatcodes
end

setmetatable(verbatim, { __index = indexer, __call = caller } )

-- metafun (this will move to another file)

local metafun = { } context.metafun = metafun

local mpdrawing = "\\MPdrawing"

local function caller(parent,f,a,...)
    if not parent then
        -- skip
    elseif f then
        local typ = type(f)
        if typ == "string" then
            if a then
                flush(currentcatcodes,mpdrawing,"{",format(f,a,...),"}")
            else
                flush(currentcatcodes,mpdrawing,"{",f,"}")
            end
        elseif typ == "number" then
            if a then
                flush(currentcatcodes,mpdrawing,"{",f,a,...,"}")
            else
                flush(currentcatcodes,mpdrawing,"{",f,"}")
            end
        elseif typ == "function" then
            -- ignored: a ...
            flush(currentcatcodes,mpdrawing,"{\\cldf{",store_(f),"}}")
        elseif typ == "boolean" then
            -- ignored: a ...
            if f then
                flush(currentcatcodes,mpdrawing,"{^^M}")
            else
                report_context("warning: 'metafun' gets argument 'false' which is currently unsupported")
            end
        else
            report_context("error: 'metafun' gets a weird argument '%s'",tostring(f))
        end
    end
end

setmetatable(metafun, { __call = caller } )

function metafun.start()
    context.resetMPdrawing()
end

function metafun.stop()
    context.MPdrawingdonetrue()
    context.getMPdrawing()
end

function metafun.color(name)
    return format([[\MPcolor{%s}]],name)
end

-- metafun.delayed

local delayed = { } metafun.delayed = delayed

local function indexer(parent,k)
    local f = function(...)
        local a = { ... }
        return function()
            return metafun[k](unpack(a))
        end
    end
    parent[k] = f
    return f
end


local function caller(parent,...)
    local a = { ... }
    return function()
        return metafun(unpack(a))
    end
end

setmetatable(delayed, { __index = indexer, __call = caller } )

--~ Not that useful yet. Maybe something like this when the main loop
--~ is a coroutine. It also does not help taking care of nested calls.
--~ Even worse, it interferes with other mechanisms using context calls.
--~
--~ local create, yield, resume = coroutine.create, coroutine.yield, coroutine.resume
--~ local getflush, setflush = context.getflush, context.setflush
--~ local texsprint, ctxcatcodes = tex.sprint, tex.ctxcatcodes
--~
--~ function context.getflush()
--~     return flush
--~ end
--~
--~ function context.setflush(newflush)
--~     local oldflush = flush
--~     flush = newflush or flush
--~     return oldflush
--~ end
--~
--~ function context.direct(f)
--~     local routine = create(f)
--~     local oldflush = getflush()
--~     function newflush(...)
--~         oldflush(...)
--~         yield(true)
--~     end
--~     setflush(newflush)
--~
--~  -- local function resumecontext()
--~  --     local done = resume(routine)
--~  --     if not done then
--~  --         return
--~  --     end
--~  --     resumecontext() -- stack overflow ... no tail recursion
--~  -- end
--~  -- context.resume = resumecontext
--~  -- texsprint(ctxcatcodes,"\\ctxlua{context.resume()}")
--~
--~     local function resumecontext()
--~         local done = resume(routine)
--~         if not done then
--~             return
--~         end
--~      -- texsprint(ctxcatcodes,"\\exitloop")
--~         texsprint(ctxcatcodes,"\\ctxlua{context.resume()}") -- can be simple macro call
--~     end
--~     context.resume = resumecontext
--~  -- texsprint(ctxcatcodes,"\\doloop{\\ctxlua{context.resume()}}") -- can be fast loop at the tex end
--~     texsprint(ctxcatcodes,"\\ctxlua{context.resume()}")
--~
--~ end
--~
--~ function something()
--~     context("\\setbox0")
--~     context("\\hbox{hans hagen xx}")
--~     context("\\the\\wd0/\\box0")
--~ end
--~
--~ context.direct(something)

-- helpers:

function context.concat(t,separator)
    local done = false
    for i=1,#t do
        local ti = t[i]
        if ti ~= "" then
            if done then
                context(separator)
            end
            context(ti)
            done = true
        end
    end
end
