if not modules then modules = { } end modules ['mlib-lmp'] = {
    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",
}

-- path relates stuff ... todo: use a stack (or numeric index to list)

local type, tonumber, tostring = type, tonumber, tostring
local setmetatable, setmetatableindex = setmetatable, table.setmetatableindex
local find, match = string.find, string.match
local insert, remove, sort = table.insert, table.remove, table.sort

local aux            = mp.aux
local mpnumeric      = aux.numeric
local mppair         = aux.pair

local registerdirect = metapost.registerdirect
local registerscript = metapost.registerscript

local scan           = mp.scan
local skip           = mp.skip
local get            = mp.get
local inject         = mp.inject

local scannumber     = scan.number
local scanstring     = scan.string
local scaninteger    = scan.integer
local scannumeric    = scan.numeric
local scanwhatever   = scan.whatever
local scanpath       = scan.path
local scanproperty   = scan.property

local gethashentry   = get.hashentry

local bpfactor       <const> = number.dimenfactors.bp

local injectwhatever = inject.whatever
local injectboolean  = inject.boolean
local injectnumeric  = inject.numeric
local injectstring   = inject.string
local injectpair     = inject.pair
local injectpath     = inject.path

local injectwhd      = inject.whd -- scaled
local injectxy       = inject.xy
local injectpt       = inject.pt

local report         = logs.reporter("metapost", "log")
local report_message = logs.reporter("metapost")

local codes          = metapost.codes
local types          = metapost.types
local procodes       = mplib.propertycodes

local implement      = interfaces.implement

do

    local function s(a,b)
        local aa = a[1]
        local bb = b[1]
        if aa == bb then
            aa = a[2]
            bb = b[2]
        end
        return aa < bb
    end

    registerscript("sortedpath", function()
        local p = scanpath()
        for i=1,#p do
            local pi = p[i]
            p[i] = { pi[1], pi[2] }
        end
        sort(p,s)
        injectpath(p)
    end)

    registerscript("uniquepath", function()
        local p = scanpath()
        local u = { }
        local n = 0
        local xx = nil
        local yy = nil
        sort(p,s)
        for i=1,#p do
            local pi = p[i]
            local x = pi[1]
            local y = pi[2]
            if x ~= xx or y ~= yy then
                n = n + 1
                u[n] = { x, y }
                xx = x
                yy = y
            end
        end
        injectpath(u)
    end)

end

do

    local p = nil
    local n = 0

    registerscript("pathreset", function()
        p = nil
        n = 0
    end)

    registerdirect("pathlengthof", function()
        p = scanpath()
        n = p and #p or 1
        return n
    end)

    registerdirect("pathpointof", function()
        local i = scaninteger()
        if i > 0 and i <= n then
            local pi = p[i]
            injectpair(pi[1],pi[2])
        end
    end)

    registerdirect("pathleftof", function()
        local i = scaninteger()
        if i > 0 and i <= n then
            local pi = p[i]
            injectpair(pi[5],pi[6])
        end
    end)

    registerdirect("pathrightof", function()
        local i = scaninteger()
        if i > 0 and i <= n then
            local pn
            if i == 1 then
                pn = p[2] or p[1]
            else
                pn = p[i+1] or p[1]
            end
            injectpair(pn[3],pn[4])
        end
    end)

end

registerscript("showproperty", function()
    local k, s, p, d = scanproperty()
    if k then
        report("name %a, property %a, command %a, detail %a",s,procodes[p] or "-",codes[k] or "-",types[d] or "-")
    end
end)

registerscript("showhashentry", function()
    local s = scanstring()
    if s then
        local k, p, d = gethashentry(s)
        if k then
            report("name %a, property %a, command %a, detail %a",s,procodes[p] or "-",codes[k] or "-",types[d] or "-")
        end
    end
end)

-- local getmacro      = tokens.getters.macro
-- local mpgnamespace  = getmacro("??graphicvariable")

-- registerscript("mpv_numeric",   function() injectnumeric  (getmacro(mpgnamespace .. getmacro("currentmpcategory") .. ":" .. scanmpstring())) end)
-- registerscript("mpv_dimension", function() return getmacro(mpgnamespace .. getmacro("currentmpcategory") .. ":" .. scanmpstring()) end)
-- registerscript("mpv_string",    function() injectstring   (getmacro(mpgnamespace .. getmacro("currentmpcategory") .. ":" .. scanmpstring())) end)

-- registerscript("mpvar", function() return getmacro(mpgnamespace .. getmacro("currentmpcategory") .. ":" .. scanmpstring(), true) end) -- Isn't it already edef'd?
-- registerscript("mpvar", function() return getmacro(metapost.namespace .. scanmpstring(), true) end) -- Isn't it already edef'd?

do

    local expandtex       = mp.expandtex

    local tokenvalues     = tokens.values
    local dimension_value <const> = tokenvalues.dimension
    local integer_value   <const> = tokenvalues.integer
    local boolean_value   <const> = tokenvalues.boolean
    local string_value    <const> = tokenvalues.string
    local unknown_value   <const> = tokenvalues.none

    registerdirect("mpvard", function()
        if not expandtex(dimension_value,"mpcategoryparameter",true,scanstring()) then
            injectnumeric(0)
        end
    end)

    registerdirect("mpvarn", function()
        if not expandtex(integer_value,"mpcategoryparameter",true,scanstring()) then
            injectnumeric(0)
        end
    end)

    registerdirect("mpvars", function()
        if not expandtex(string_value,"mpcategoryparameter",true,scanstring()) then
            injectstring("")
        end
    end)

    registerdirect("mpvarb", function()
        if not expandtex(boolean_value,"mpcategoryparameter",true,scanstring()) then
            injectboolean(false)
        end
    end)

    registerdirect("mpvar", function()
        if not expandtex(unknown_value,"mpcategoryparameter",true,scanstring()) then
            injectnumeric(0)
        end
    end)

    -- older:

    registerscript("texvar", function()
        if not expandtex(unknown_value,"mpcategoryparameter",true,scanstring()) then
            injectnumeric(0)
        end
    end)

    registerscript("texstr", function()
        if not expandtex(string_value,"mpcategoryparameter",true,scanstring()) then
            injectstring("")
        end
    end)

end

do

    registerscript("textextanchor", function()
        local x, y = match(scanstring(),"tx_anchor=(%S+) (%S+)") -- todo: make an lpeg
        if x and y then
            x = tonumber(x)
            y = tonumber(y)
        end
        injectpair(x or 0,y or 0)
    end)

end

do

    local mpnamedcolor = attributes.colors.mpnamedcolor
    local mpprint      = mp.aux.print

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

    -- todo: we can inject but currently we always get a string back so then
    -- we need to deal with it upstream in the color module ... not now

    registerscript("namedcolor",function() mpprint(mpnamedcolor(scanstring())) end)

end

do

    local hashes = setmetatableindex("table")

    -- There is no need to 'new' a hash in which case one can use any reasonable
    -- tag. The registry aproach is mkiv compatible and not used in mkxl (lmtx).

    local registry = { }
    local count    = 0

    registerdirect("lmt_hash_new", function()
        local slot = false
        for i=1,count do
            if registry[i] then
                slot = i
                break
            end
        end
        if not slot then
            count = count + 1
            slot  = count
        end
        registry[slot] = true
        hashes[slot] = nil
        injectwhatever(slot)
    end)

    registerdirect("lmt_hash_dispose", function()
        local name = scanwhatever()
        hashes[name] = nil
        -- when new'd
        if registry[name] then
            registry[name] = false
        end
    end)

    registerdirect("lmt_hash_reset", function()
        local name = scanwhatever()
        hashes[name] = nil
    end)

    registerdirect("lmt_hash_in", function()
        local name = scanwhatever()
        local key  = scanwhatever()
        local hash = hashes[name]
        injectwhatever(hash and hash[key] and true or false)
    end)

    registerdirect("lmt_hash_to", function()
        local name  = scanwhatever()
        local key   = scanwhatever()
        local value = scanwhatever()
        local hash  = hashes[name]
        if hash then
            hash[key] = value
        end
    end)

    registerdirect("lmt_hash_from", function()
     -- local name  = scanstring()
        local name  = scanwhatever()
     -- local key  = scanstring()
        local key  = scanwhatever()
        local hash = hashes[name]
        injectwhatever(hash and hash[key] or false)
    end)

    interfaces.implement {
        name      = "MPfromhash",
        arguments = "2 strings",
        actions   = function(name,key)
            local hash = hashes[name] or hashes[tonumber(name)] or hashes[tostring(name)]
            if hash then
                local v = hash[key]
                if v then
                    context(v)
                end
            end
        end
    }

end

do

    local bpfactor     = number.dimenfactors.bp
    local nbdimensions = nodes.boxes.dimensions

    registerdirect("boxdimensions", function()
        local category = scanstring()
        local index    = scanwhatever()
        injectwhd(nbdimensions(category,index))
    end)

end

do

    local skiptoken      = skip.token

    local comma_code     <const> = codes.comma

    local getmacro       = tokens.getters.macro
    local setmacro       = tokens.setters.macro

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

    -- more helpers

    registerdirect("getmacro", function() return getmacro(scanstring()) end)
    registerdirect("getcount", function() return getcount(scanwhatever()) end)
    registerdirect("gettoks",  function() return gettoks (scanwhatever()) end)
    registerdirect("getdimen", function() return getdimen(scanwhatever()) * bpfactor end)

    registerscript("setmacro", function() setmacro(scanstring(),scanstring()) end)
    registerscript("setdimen", function() setdimen(scanwhatever(),scannumeric()/bpfactor) end)
    registerscript("setcount", function() setcount(scanwhatever(),scannumeric()) end)
    registerscript("settoks",  function() settoks (scanwhatever(),scanstring()) end)

    registerscript("setglobalmacro", function() setmacro(scanstring(),scanstring(),"global") end)
    registerscript("setglobaldimen", function() setdimen("global",scanwhatever(),scannumeric()/bpfactor) end)
    registerscript("setglobalcount", function() setcount("global",scanwhatever(),scaninteger()) end)
    registerscript("setglobaltoks",  function() settoks ("global",scanwhatever(),scanstring()) end)

    local utfnum = utf.byte
    local utfchr = utf.char
    local utflen = utf.len
    local utfsub = utf.sub

    registerdirect("utfchr", function() return utfchr(scannumeric()) end)
    registerdirect("utfnum", function() return utfnum(scanstring()) end)
    registerdirect("utflen", function() return utflen(scanstring()) end)

    registerdirect("utfsub", function() -- we have an optional third argument so we explicitly scan a text argument
        return utfsub(scanstring(),skiptoken(comma_code) and scannumeric(),skiptoken(comma_code) and scannumeric())
    end)

    local setlogging = metapost.setlogging

    registerscript("message", function()
        setlogging(false)
        local str = scanstring()
        setlogging(true)
        report_message("message : %s",str)
    end)

end

-- position fun

do

    local getcount       = tex.getcount

    local mpprint        = mp.print
    local mpfprint       = mp.fprint

    local mpscaninteger  = mp.scan.integer
    local mpscannumber   = mp.scan.number

    local jobpositions   = job.positions
    local getwhd         = jobpositions.whd
    local getxy          = jobpositions.xy
    local getx           = jobpositions.x
    local gety           = jobpositions.y
    local getposition    = jobpositions.position
    local getpage        = jobpositions.page
    local getparagraph   = jobpositions.paragraph
    local getregion      = jobpositions.region
    local getcolumn      = jobpositions.column
    local getmacro       = tokens.getters.macro

    local columnofpos    = jobpositions.columnofpos
    local getcolumndata  = jobpositions.getcolumndata
 -- local overlapping    = jobpositions.overlapping
 -- local onsamepage     = jobpositions.onsamepage
 -- local columnofpos    = jobpositions.columnofpos

    -- why not inject path directly

    registerscript("positionpath", function()
        local w, h, d = getwhd(scanstring())
        if w then
            mpfprint("((%p,%p)--(%p,%p)--(%p,%p)--(%p,%p)--cycle)",0,-d,w,-d,w,h,0,h)
        else
            mpprint("(origin--cycle)")
        end
    end)

    registerscript("positioncurve", function()
        local w, h, d = getwhd(scanstring())
        if w then
            mpfprint("((%p,%p)..(%p,%p)..(%p,%p)..(%p,%p)..cycle)",0,-d,w,-d,w,h,0,h)
        else
            mpprint("(origin--cycle)")
        end
    end)

    registerscript("positionbox", function()
        local p, x, y, w, h, d = getposition(scanstring())
        if p then
            mpfprint("((%p,%p)--(%p,%p)--(%p,%p)--(%p,%p)--cycle)",x,y-d,x+w,y-d,x+w,y+h,x,y+h)
        else
            mpprint("(%p,%p)--cycle",x or 0,y or 0)
        end
    end)

    registerscript("positioncolumnbox", function()
        local column = mpscaninteger()
        local data   = getcolumndata(getcount("realpageno"),column)
        if data then
            local x, y, w, h, d = data.x, data.y, data.w, data.h, data.d
            mpfprint("((%p,%p)--(%p,%p)--(%p,%p)--(%p,%p)--cycle)",x,y-d,x+w,y-d,x+w,y+h,x,y+h)
        else
            mpprint("(0,0)--cycle")
        end
    end)

    registerscript("overlaycolumnbox", function()
        local column = mpscaninteger()
        local data   = getcolumndata(getcount("realpageno"),column)
        if data then
            local w, hd = data.w, data.h + data.d
            mpfprint("((0,0)--(%p,0)--(%p,%p)--(0,%p)--cycle)",w,w,hd,hd)
        else
            mpprint("(0,0)--cycle")
        end
    end)

    registerdirect("positionpage",      function() return getpage     (scanstring()) or 0 end)
    registerdirect("positioncolumn",    function() return getcolumn   (scanstring()) or 0 end)
    registerdirect("positionparagraph", function() return getparagraph(scanstring()) or 0 end)
    registerdirect("positionregion",    function() return getregion   (scanstring()) or "unknown" end)
    registerdirect("positionanchor",    function() return getmacro    ("MPanchorid") end)
    registerdirect("positionwhd",       function() injectwhd(getwhd(scanstring())) end)
    registerdirect("positionxy",        function() injectxy (getxy (scanstring())) end)
    registerdirect("positionx",         function() injectpt (getx  (scanstring())) end)
    registerdirect("positiony",         function() injectpt (gety  (scanstring())) end)

    registerdirect("positioncolumnatx", function()
        local realpage  = mpscaninteger()
        local xposition = mpscannumber()
        return columnofpos(realpage,xposition)
    end)

end

do

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

    registerdirect("mode",       function() injectboolean(modes      [scanstring()] and true or false) end)
    registerdirect("systemmode", function() injectboolean(systemmodes[scanstring()] and true or false) end)

    -- for compatibility reasons we keep this (metafun manual):

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

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

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

    mp.processingmode = mp.mode

end

-- for alan's nodes:

do

    local lpegmatch, lpegpatterns, P = lpeg.match, lpeg.patterns, lpeg.P

    -- todo: scansuffix / why no return boolean (first one)

    registerdirect("isarray", function()
         injectboolean(find(scanstring(),"%d") and true or false)
    end)

    registerdirect("prefix", function()
        local str = scanstring()
        return match(str,"^(.-)[%d%[]") or str
    end)

    local dimension = lpeg.counter(P("[") * lpegpatterns.integer * P("]") + lpegpatterns.integer)

    registerdirect("dimension", function() return dimension(scanstring()) end)

    -- todo : share with mlib-pps.lua metapost,isobject

    -- registerdirect("isobject", function()
    --     injectboolean(find(scanstring(),"mf_object="))
    -- end

    local p1      = P("mf_object=")
    local p2      = lpegpatterns.eol * p1
    local pattern = (1-p2)^0 * p2 + p1

    registerdirect("isobject", function()
        local str = scanstring()
        injectboolean(pattern and str ~= "" and lpegmatch(pattern,str))
    end)

end

-- key/values (moved here, old mechanism)

do

    local stack, top = { }, nil

    local function setvariable(k,v)
        if top then
            top[k] = v
        else
            metapost.variables[k] = v
        end
    end

    local function pushvariable(k)
        local t = { }
        if top then
            insert(stack,top)
            top[k] = t
        else
            metapost.variables[k] = t
        end
        top = t
    end

    local function popvariable()
        top = remove(stack)
    end

    registerscript("passvariable", function() setvariable (scanstring(), scanwhatever()) end)
    registerscript("pushvariable", function() pushvariable(scanstring()) end)
    registerscript("popvariable",  function() popvariable () end)

    local stack = { }

    local function pushvariables()
        insert(stack,metapost.variables)
        metapost.variables = { }
    end

    local function popvariables()
        metapost.variables = remove(stack) or metapost.variables
    end

    metapost.setvariable   = setvariable
    metapost.pushvariable  = pushvariable
    metapost.popvariable   = popvariable
    metapost.pushvariables = pushvariables
    metapost.popvariables  = popvariables

    implement {
        name      = "mppushvariables",
        actions   = pushvariables,
    }

    implement {
        name      = "mppopvariables",
        actions   = popvariables,
    }

end

do

    local repeatable = utilities.randomizer.repeatable

    registerdirect("repeatablerandom", function()
        return repeatable(scanstring())
    end)

end

do

    local hascurvature = metapost.hascurvature

    registerdirect("hascurvature",
        function()
            local p = scanpath()    -- pathsegment
            local t = scannumeric() -- tolerance
            local l = p[1]
            local r = p[#p]
            injectboolean(l and r and hascurvature (
                {
                    x_coord = l[1], y_coord = l[2],
                    left_x  = l[3], left_y  = l[4],
                    right_x = l[5], right_y = l[6],
                }, {
                    x_coord = r[1], y_coord = r[2],
                    left_x  = r[3], left_y  = r[4],
                    right_x = r[5], right_y = r[6],
                },
                t
            ) or false)
        end
    )

end

do

    local defaults = {
        noplugins = false
    }

    local backendoptions = setmetatableindex(function(t,k) local v = setmetatable({},defaults) t[k] = v return v end)

    function metapost.resetbackendoptions(mpx)
        backendoptions[mpx] = setmetatable({},defaults)
    end

    function metapost.getbackendoption(mpx,name)
        return backendoptions[mpx][name]
    end

    registerdirect("setbackendoption",
        function()
            backendoptions[metapost.currentmpx()][scanstring()] = true
        end
    )
end

do

    registerdirect("namedstacking", function()
        injectnumeric(typesetters.stacking.getindex(scanstring()))
    end)

end
