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

-- This is very preliminary code!

-- maybe we need mplib.model, but how with instances

local type, tostring, select, loadstring = type, tostring, select, loadstring
local find, match, gsub, gmatch = string.find, string.match, string.gsub, string.gmatch

local formatters = string.formatters
local concat     = table.concat
local lpegmatch  = lpeg.match

local P, S, Ct = lpeg.P, lpeg.S, lpeg.Ct

local report_luarun  = logs.reporter("metapost","lua")
local report_message = logs.reporter("metapost")

local trace_luarun   = false  trackers.register("metapost.lua",function(v) trace_luarun = v end)
local trace_enabled  = true

local be_tolerant    = true   directives.register("metapost.lua.tolerant",function(v) be_tolerant = v end)

mp = mp or { } -- system namespace
MP = MP or { } -- user namespace

local buffer, n, max = { }, 0, 10 -- we reuse upto max

function mp._f_()
    if trace_enabled and trace_luarun then
        local result = concat(buffer," ",1,n)
        if n > max then
            buffer = { }
        end
        n = 0
        report_luarun("data: %s",result)
        return result
    else
        if n == 0 then
            return ""
        end
        local result
        if n == 1 then
            result = buffer[1]
        else
            result = concat(buffer," ",1,n)
        end
        if n > max then
            buffer = { }
        end
        n = 0
        return result
    end
end

local f_code      = formatters["%s return mp._f_()"]

local f_numeric   = formatters["%.16f"]
local f_integer   = formatters["%i"]
local f_pair      = formatters["(%.16f,%.16f)"]
local f_triplet   = formatters["(%.16f,%.16f,%.16f)"]
local f_quadruple = formatters["(%.16f,%.16f,%.16f,%.16f)"]

local function mpprint(...)
    for i=1,select("#",...) do
        local value = select(i,...)
        if value ~= nil then
            n = n + 1
            local t = type(value)
            if t == "number" then
                buffer[n] = f_numeric(value)
            elseif t == "string" then
                buffer[n] = value
            elseif t == "table" then
                buffer[n] = "(" .. concat(value,",") .. ")"
            else -- boolean or whatever
                buffer[n] = tostring(value)
            end
        end
    end
end

mp.print = mpprint

-- We had this:
--
--   table.setmetatablecall(mp,function(t,k) mpprint(k) end)
--
-- but the next one is more interesting because we cannot use calls like:
--
--   lua.mp.somedefdname("foo")
--
-- which is due to expansion of somedefdname during suffix creation. So:
--
--   lua.mp("somedefdname","foo")

table.setmetatablecall(mp,function(t,k,...) return t[k](...) end)

function mp.boolean(b)
    n = n + 1
    buffer[n] = b and "true" or "false"
end

function mp.numeric(f)
    n = n + 1
    buffer[n] = f and f_numeric(f) or "0"
end

function mp.integer(i)
    n = n + 1
 -- buffer[n] = i and f_integer(i) or "0"
    buffer[n] = i or "0"
end

function mp.pair(x,y)
    n = n + 1
    if type(x) == "table" then
        buffer[n] = f_pair(x[1],x[2])
    else
        buffer[n] = f_pair(x,y)
    end
end

function mp.triplet(x,y,z)
    n = n + 1
    if type(x) == "table" then
        buffer[n] = f_triplet(x[1],x[2],x[3])
    else
        buffer[n] = f_triplet(x,y,z)
    end
end

function mp.quadruple(w,x,y,z)
    n = n + 1
    if type(w) == "table" then
        buffer[n] = f_quadruple(w[1],w[2],w[3],w[4])
    else
        buffer[n] = f_quadruple(w,x,y,z)
    end
end

function mp.path(t,connector,cycle)
    if type(t) == "table" then
        local tn = #t
        if tn > 0 then
            if connector == true then
                connector = "--"
                cycle     = true
            elseif not connector then
                connector = "--"
            end
            local ti = t[1]
            n = n + 1 ; buffer[n] = f_pair(ti[1],ti[2])
            for i=2,tn do
                local ti = t[i]
                n = n + 1 ; buffer[n] = connector
                n = n + 1 ; buffer[n] = f_pair(ti[1],ti[2])
            end
            if cycle then
                n = n + 1 ; buffer[n] = connector
                n = n + 1 ; buffer[n] = "cycle"
            end
        end
    end
end

function mp.size(t)
    n = n + 1
    buffer[n] = type(t) == "table" and f_numeric(#t) or "0"
end

local mpnamedcolor = attributes.colors.mpnamedcolor

mp.NamedColor = function(str)
    mpprint(mpnamedcolor(str))
end

-- experiment: names can change

local datasets = { }
mp.datasets    = datasets

function datasets.load(tag,filename)
    if not filename then
        tag, filename = file.basename(tag), tag
    end
    local data = mp.dataset(io.loaddata(filename) or "")
    datasets[tag] = {
        Data = data,
        Line = function(n) mp.path(data[n or 1]) end,
        Size = function()  mp.size(data)         end,
    }
end

--

local replacer = lpeg.replacer("@","%%")

function mp.fprint(fmt,...)
    n = n + 1
    if not find(fmt,"%%") then
        fmt = lpegmatch(replacer,fmt)
    end
    buffer[n] = formatters[fmt](...)
end

local function mpquoted(fmt,s,...)
    n = n + 1
    if s then
        if not find(fmt,"%%") then
            fmt = lpegmatch(replacer,fmt)
        end
        buffer[n] = '"' .. formatters[fmt](s,...) .. '"'
    elseif fmt then
        buffer[n] = '"' .. fmt .. '"'
    else
        -- something is wrong
    end
end

mp.quoted = mpquoted

function mp.n(t)
    return type(t) == "table" and #t or 0
end

local whitespace = lpeg.patterns.whitespace
local newline    = lpeg.patterns.newline
local setsep     = newline^2
local comment    = (S("#%") + P("--")) * (1-newline)^0 * (whitespace - setsep)^0
local value      = (1-whitespace)^1 / tonumber
local entry      = Ct( value * whitespace * value)
local set        = Ct((entry * (whitespace-setsep)^0 * comment^0)^1)
local series     = Ct((set * whitespace^0)^1)

local pattern    = whitespace^0 * series

function mp.dataset(str)
    return lpegmatch(pattern,str)
end

-- \startluacode
--     local str = [[
--         10 20 20 20
--         30 40 40 60
--         50 10
--
--         10 10 20 30
--         30 50 40 50
--         50 20 -- the last one
--
--         10 20 % comment
--         20 10
--         30 40 # comment
--         40 20
--         50 10
--     ]]
--
--     MP.myset = mp.dataset(str)
--
--     inspect(MP.myset)
-- \stopluacode
--
-- \startMPpage
--     color c[] ; c[1] := red ; c[2] := green ; c[3] := blue ;
--     for i=1 upto lua("mp.print(mp.n(MP.myset))") :
--         draw lua("mp.path(MP.myset[" & decimal i & "])") withcolor c[i] ;
--     endfor ;
-- \stopMPpage

-- function metapost.runscript(code)
--     local f = loadstring(f_code(code))
--     if f then
--         local result = f()
--         if result then
--             local t = type(result)
--             if t == "number" then
--                 return f_numeric(result)
--             elseif t == "string" then
--                 return result
--             else
--                 return tostring(result)
--             end
--         end
--     end
--     return ""
-- end

local cache, n = { }, 0 -- todo: when > n then reset cache or make weak

function metapost.runscript(code)
    local trace = trace_enabled and trace_luarun
    if trace then
        report_luarun("code: %s",code)
    end
    local f
    if n > 100 then
        cache = nil -- forget about caching
        f = loadstring(f_code(code))
        if not f and be_tolerant then
            f = loadstring(code)
        end
    else
        f = cache[code]
        if not f then
            f = loadstring(f_code(code))
            if f then
                n = n + 1
                cache[code] = f
            elseif be_tolerant then
                f = loadstring(code)
                if f then
                    n = n + 1
                    cache[code] = f
                end
            end
        end
    end
    if f then
        local result = f()
        if result then
            local t = type(result)
            if t == "number" then
                result = f_numeric(result)
            elseif t ~= "string" then
                result = tostring(result)
            end
            if trace then
                report_luarun("result: %s",result)
            end
            return result
        elseif trace then
            report_luarun("no result")
        end
    else
        report_luarun("no result, invalid code")
    end
    return ""
end

-- function metapost.initializescriptrunner(mpx)
--     mp.numeric = function(s) return mpx:get_numeric(s) end
--     mp.string  = function(s) return mpx:get_string (s) end
--     mp.boolean = function(s) return mpx:get_boolean(s) end
--     mp.number  = mp.numeric
-- end

local get_numeric = mplib.get_numeric
local get_string  = mplib.get_string
local get_boolean = mplib.get_boolean
local get_number  = get_numeric

-- function metapost.initializescriptrunner(mpx)
--     mp.numeric = function(s) return get_numeric(mpx,s) end
--     mp.string  = function(s) return get_string (mpx,s) end
--     mp.boolean = function(s) return get_boolean(mpx,s) end
--     mp.number  = mp.numeric
-- end

local currentmpx = nil

local get = { }
mp.get    = get

get.numeric = function(s) return get_numeric(currentmpx,s) end
get.string  = function(s) return get_string (currentmpx,s) end
get.boolean = function(s) return get_boolean(currentmpx,s) end
get.number  = mp.numeric

function metapost.initializescriptrunner(mpx,trialrun)
    currentmpx = mpx
    if trace_luarun then
        report_luarun("type of run: %s", trialrun and "trial" or "final")
    end
 -- trace_enabled = not trialrun blocks too much
end

-- texts:

local factor       = 65536*(7227/7200)
local textexts     = nil
local mptriplet    = mp.triplet
local nbdimensions = nodes.boxes.dimensions

function mp.tt_initialize(tt)
    textexts = tt
end

-- function mp.tt_wd(n)
--     local box = textexts and textexts[n]
--     mpprint(box and box.width/factor or 0)
-- end
-- function mp.tt_ht(n)
--     local box = textexts and textexts[n]
--     mpprint(box and box.height/factor or 0)
-- end
-- function mp.tt_dp(n)
--     local box = textexts and textexts[n]
--     mpprint(box and box.depth/factor or 0)
-- end

function mp.tt_dimensions(n)
    local box = textexts and textexts[n]
    if box then
        -- could be made faster with nuts but not critical
        mptriplet(box.width/factor,box.height/factor,box.depth/factor)
    else
        mptriplet(0,0,0)
    end
end

function mp.tb_dimensions(category,name)
    local w, h, d = nbdimensions(category,name)
    mptriplet(w/factor,h/factor,d/factor)
end

function mp.report(a,b)
    if b then
        report_message("%s : %s",a,b)
    elseif a then
        report_message("%s : %s","message",a)
    end
end

--

local hashes = { }

function mp.newhash()
    for i=1,#hashes+1 do
        if not hashes[i] then
            hashes[i] = { }
            mpprint(i)
            return
        end
    end
end

function mp.disposehash(n)
    hashes[n] = nil
end

function mp.inhash(n,key)
    local h = hashes[n]
    mpprint(h and h[key] and true or false)
end

function mp.tohash(n,key)
    local h = hashes[n]
    if h then
        h[key] = true
    end
end

local modes       = tex.modes
local systemmodes = tex.systemmodes

function mp.mode(s)
    mpprint(modes[s] and true or false)
end

function mp.systemmode(s)
    mpprint(systemmodes[s] and true or false)
end

-- for alan's nodes:

function mp.isarray(str)
     mpprint(find(str,"%d") and true or false)
end

function mp.prefix(str)
     mpquoted(match(str,"^(.-)[%d%[]") or str)
end

function mp.dimensions(str)
    local n = 0
    for s in gmatch(str,"%[?%-?%d+%]?") do --todo: lpeg
        n = n + 1
    end
    mpprint(n)
end

-- faster and okay as we don't have many variables but probably only
-- basename makes sense and even then it's not called that often

-- local hash  = table.setmetatableindex(function(t,k)
--     local v = find(k,"%d") and true or false
--     t[k] = v
--     return v
-- end)
--
-- function mp.isarray(str)
--      mpprint(hash[str])
-- end
--
-- local hash  = table.setmetatableindex(function(t,k)
--     local v = '"' .. (match(k,"^(.-)%d") or k) .. '"'
--     t[k] = v
--     return v
-- end)
--
-- function mp.prefix(str)
--      mpprint(hash[str])
-- end

local getdimen  = tex.getdimen
local getcount  = tex.getcount
local gettoks   = tex.gettoks
local setdimen  = tex.setdimen
local setcount  = tex.setcount
local settoks   = tex.settoks

local mpprint   = mp.print
local mpquoted  = mp.quoted

local factor    = number.dimenfactors.bp

-- more helpers

function mp.getdimen(k)   mpprint (getdimen(k)*factor) end
function mp.getcount(k)   mpprint (getcount(k)) end
function mp.gettoks (k)   mpquoted(gettoks (k)) end
function mp.setdimen(k,v) setdimen(k,v/factor) end
function mp.setcount(k,v) setcount(k,v) end
function mp.settoks (k,v) settoks (k,v) end

-- def foo = lua.mp.foo ... enddef ; % loops due to foo in suffix

mp._get_dimen_ = mp.getdimen
mp._get_count_ = mp.getcount
mp._get_toks_  = mp.gettoks
mp._set_dimen_ = mp.setdimen
mp._set_count_ = mp.setcount
mp._set_toks_  = mp.settoks

-- position fun

do

    local mprint      = mp.print
    local fprint      = mp.fprint
    local qprint      = mp.quoted
    local getwhd      = job.positions.whd
    local getxy       = job.positions.xy
    local getposition = job.positions.position
    local getpage     = job.positions.page
    local getregion   = job.positions.region
    local getmacro    = tokens.getters.macro

    function mp.positionpath(name)
        local w, h, d = getwhd(name)
        if w then
            fprint("((%p,%p)--(%p,%p)--(%p,%p)--(%p,%p)--cycle)",0,-d,w,-d,w,h,0,h)
        else
            mprint("(origin--cycle)")
        end
    end

    function mp.positioncurve(name)
        local w, h, d = getwhd(name)
        if w then
            fprint("((%p,%p)..(%p,%p)..(%p,%p)..(%p,%p)..cycle)",0,-d,w,-d,w,h,0,h)
        else
            mprint("(origin--cycle)")
        end
    end

    function mp.positionbox(name)
        local p, x, y, w, h, d = getposition(name)
        if p then
            fprint("((%p,%p)--(%p,%p)--(%p,%p)--(%p,%p)--cycle)",x,y-d,x+w,y-d,x+w,y+h,x,y+h)
        else
            mprint("(%p,%p)",x,y)
        end
    end

    function mp.positionxy(name)
        local x, y = getxy(name)
        if x then
            fprint("(%p,%p)",x,y)
        else
            mprint("origin")
        end
    end

    function mp.positionpage(name)
        local p = getpage(name)
        if p then
            fprint("%p",p)
        else
            mprint("0")
        end
    end

    function mp.positionregion(name)
        local r = getregion(name)
        if r then
            qprint(r)
        else
            qprint("unknown")
        end
    end

    function mp.positionwhd(name)
        local w, h, d = getwhd(name)
        if w then
            fprint("(%p,%p,%p)",w,h,d)
        else
            mprint("(0,0,0)")
        end
    end

    function mp.positionpxy(name)
        local p, x, y = getposition(name)
        if p then
            fprint("(%p,%p,%p)",p,x,y)
        else
            mprint("(0,0,0)")
        end
    end

    function mp.positionanchor()
        qprint(getmacro("MPanchorid"))
    end

end

do

    local mprint   = mp.print
    local qprint   = mp.quoted
    local getmacro = tokens.getters.macro

    function mp.texvar(name)
        mprint(getmacro(metapost.namespace .. name))
    end

    function mp.texstr(name)
        qprint(getmacro(metapost.namespace .. name))
    end

end
