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

-- The directional helpers and pen analysis are more or less translated from the C
-- code. In LuaTeX we spent quite some time on speeding up the Lua interface as well
-- as the C code. There is not much to gain, especially if one keeps in mind that
-- when integrated in TeX only a part of the time is spent in MetaPost. Of course an
-- integrated approach is way faster than an external MetaPost and processing time
-- nears zero.
--
-- In LuaMetaTeX the MetaPost core has been cleaned up a it and as a result
-- processing in double mode is now faster than in scaled mode. There are also extra
-- features and interfaces, so the MkIV and MkXL (LMTX) implementation differ!

local type, tostring, tonumber, next = type, tostring, tonumber, next
local find, striplines = string.find, utilities.strings.striplines
local concat, insert, remove, sortedkeys = table.concat, table.insert, table.remove, table.sortedkeys
local abs = math.abs

local emptystring = string.is_empty

local trace_graphics   = false  trackers.register("metapost.graphics",   function(v) trace_graphics   = v end)
local trace_tracingall = false  trackers.register("metapost.tracingall", function(v) trace_tracingall = v end)

local texerrormessage = logs.texerrormessage

local report_metapost = logs.reporter("metapost")
local report_terminal = logs.reporter("metapost","terminal")
local report_tracer   = logs.reporter("metapost","trace")
local report_error    = logs.reporter("metapost","error")

local starttiming     = statistics.starttiming
local stoptiming      = statistics.stoptiming

local formatters      = string.formatters

local mplib           = mplib
metapost              = metapost or { }
local metapost        = metapost

metapost.showlog      = false
metapost.lastlog      = ""
metapost.texerrors    = false
metapost.quitonerror  = true
metapost.exectime     = metapost.exectime or { } -- hack
metapost.nofruns      = 0

local mpxformats      = { }
local mpxnames        = { }
local nofformats      = 0
local mpxpreambles    = { }
local mpxterminals    = { }
local mpxextradata    = { }

-- The flatten hack is needed because the library currently barks on \n\n and the
-- collapse because mp cannot handle snippets due to grouping issues.

-- todo: pass tables to executempx instead of preparing beforehand,
-- as it's more efficient for the terminal

local function flatten(source,target)
    for i=1,#source do
        local d = source[i]
        if type(d) == "table" then
            flatten(d,target)
        elseif d and d ~= "" then
            target[#target+1] = d
        end
    end
    return target
end

local function prepareddata(data)
    if data and data ~= "" then
        if type(data) == "table" then
            data = flatten(data,{ })
            data = #data > 1 and concat(data,"\n") or data[1]
        end
        return data
    end
end

local execute = mplib.execute

local function executempx(mpx,data)
    local terminal = mpxterminals[mpx]
    if terminal then
        terminal.writer(data)
        data = nil
    elseif type(data) == "table" then
        data = prepareddata(data,collapse)
    end
    metapost.nofruns = metapost.nofruns + 1
    local result = execute(mpx,data)
    return result
end

directives.register("mplib.quitonerror", function(v) metapost.quitonerror = v end)
directives.register("mplib.texerrors",   function(v) metapost.texerrors   = v end)
trackers  .register("metapost.showlog",  function(v) metapost.showlog     = v end)

function metapost.resetlastlog()
    metapost.lastlog = ""
end

local new_instance = mplib.new
local find_file    = mplib.finder

function metapost.reporterror(result)
    if not result then
        report_metapost("error: no result object returned")
        return true
    elseif result.status == 0 then
        return false
    elseif mplib.realtimelogging then
        return false -- we already reported
    else
        local t = result.term
        local e = result.error
        local l = result.log
        local report = metapost.texerrors and texerrormessage or report_metapost
        if t and t ~= "" then
            report("mp error: %s",striplines(t))
        end
        if e == "" or e == "no-error" then
            e = nil
        end
        if e then
            report("mp error: %s",striplines(e))
        end
        if not t and not e and l then
            metapost.lastlog = metapost.lastlog .. "\n" .. l
            report_metapost("log: %s",l)
        else
            report_metapost("error: unknown, no error, terminal or log messages")
        end
        return true
    end
end

local f_preamble = formatters [ [[
    boolean mplib ; mplib := true ;
    let dump = endinput ;
    input "%s" ;
    randomseed:=%s;
]] ]

local methods = {
    double  = "double",
    scaled  = "scaled",
 -- binary  = "binary",
    binary  = "double",
    decimal = "decimal",
    posit   = "posit",
    default = "scaled",
}

function metapost.runscript(code)
    return ""
end

-- todo: random_seed

local seed = nil

local default_tolerance = 131/65536.0 -- a little below 0.001 * 0x7FFF/0x4000
local bend_tolerance    = default_tolerance
local move_tolerance    = default_tolerance

----- bend_tolerance = 10/2000
----- move_tolerance = bend_tolerance

function metapost.setbendtolerance(t)
    bend_tolerance = t or default_tolerance
end
function metapost.setmovetolerance(t)
    move_tolerance = t or default_tolerance
end
function metapost.settolerance(t)
    bend_tolerance = t or default_tolerance
    move_tolerance = t or default_tolerance
end

function metapost.getbendtolerance()
    return bend_tolerance
end
function metapost.getmovetolerance()
    return move_tolerance
end
function metapost.gettolerance(t)
    return bend_tolerance, move_tolerance
end

function metapost.load(name,method)
    starttiming(mplib)
    if not seed then
        seed = job.getrandomseed()
        if seed <= 1 then
            seed = seed % 1000
        elseif seed > 4095 then
            seed = seed % 4096
        end
    end
    method = method and methods[method] or "scaled"
    local mpx, terminal = new_instance {
        bendtolerance   = bend_tolerance,
        movetolerance   = move_tolerance,
        mathmode        = method,
        runscript       = metapost.runscript,
        runinternal     = metapost.runinternal,
        maketext        = metapost.maketext,
        handlers        = {
            log         = metapost.newlogger(),
         -- warning     = function() end,
         -- error       = function() end,
         -- status      = function() end,
        },
    }
    mpxnames[mpx] = {
        name   = name,
        method = method,
    }
    report_metapost("initializing number mode %a",method)
    local result
    if not mpx then
        result = { status = 99, error = "out of memory"}
    else
        mpxterminals[mpx] = terminal
        -- pushing permits advanced features
        metapost.pushscriptrunner(mpx)
        result = executempx(mpx,f_preamble(file.addsuffix(name,"mp"),seed))
        metapost.popscriptrunner()
    end
    stoptiming(mplib)
    metapost.reporterror(result)
    return mpx, result
end

function metapost.checkformat(mpsinput,method)
    local mpsinput  = mpsinput or "metafun"
    local foundfile = ""
    if file.suffix(mpsinput) ~= "" then
        foundfile  = find_file(mpsinput) or ""
    end
 -- if foundfile == "" then
 --     foundfile  = find_file(file.replacesuffix(mpsinput,"mpvi")) or ""
 -- end
    if foundfile == "" then
        foundfile  = find_file(file.replacesuffix(mpsinput,"mpxl")) or ""
    end
    if foundfile == "" then
        foundfile  = find_file(file.replacesuffix(mpsinput,"mpiv")) or ""
    end
    if foundfile == "" then
        foundfile  = find_file(file.replacesuffix(mpsinput,"mp")) or ""
    end
    if foundfile == "" then
        report_metapost("loading %a fails, format not found",mpsinput)
    else
        report_metapost("loading %a as %a using method %a",mpsinput,foundfile,method or "default")
        local mpx, result = metapost.load(foundfile,method)
        if mpx then
            return mpx
        else
            report_metapost("error in loading %a",mpsinput)
            metapost.reporterror(result)
        end
    end
end

function metapost.unload(mpx)
    starttiming(mplib)
    if mpx then
        mpx:finish()
    end
    stoptiming(mplib)
end

metapost.defaultformat   = "metafun"
metapost.defaultinstance = "metafun"
metapost.defaultmethod   = "default"

function metapost.getextradata(mpx)
    return mpxextradata[mpx]
end

function metapost.pushformat(specification,f,m) -- was: instance, name, method
    if type(specification) ~= "table" then
        specification = {
            instance = specification,
            format   = f,
            method   = m,
        }
    end
    local instance    = specification.instance
    local format      = specification.format
    local method      = specification.method
    local definitions = specification.definitions
    local extensions  = specification.extensions
    local preamble    = nil
    if not instance or instance == "" then
        instance = metapost.defaultinstance
        specification.instance = instance
    end
    if not format or format == "" then
        format = metapost.defaultformat
        specification.format = format
    end
    if not method or method == "" then
        method = metapost.defaultmethod
        specification.method = method
    end
    if definitions and definitions ~= "" then
        preamble = definitions
    end
    if extensions and extensions ~= "" then
        if preamble then
            preamble = preamble .. "\n" .. extensions
        else
            preamble = extensions
        end
    end
    nofformats = nofformats + 1
    local usedinstance = instance .. ":" .. nofformats
    local mpx = mpxformats  [usedinstance]
    local mpp = mpxpreambles[instance] or ""
 -- report_metapost("push instance %a (%S)",usedinstance,mpx)
    if preamble then
        preamble = prepareddata(preamble)
        mpp = mpp .. "\n" .. preamble
        mpxpreambles[instance] = mpp
    end
    if not mpx then
        report_metapost("initializing instance %a using format %a and method %a",usedinstance,format,method)
        mpx = metapost.checkformat(format,method)
        mpxformats  [usedinstance] = mpx
        mpxextradata[mpx] = { }
        if mpp ~= "" then
            preamble = mpp
        end
    end
    metapost.resetbackendoptions(mpx)
    if preamble then
        metapost.pushscriptrunner(mpx)
        executempx(mpx,preamble)
        metapost.popscriptrunner()
    end
    specification.mpx = mpx
    return mpx
end

-- luatex.wrapup(function()
--     for k, mpx in next, mpxformats do
--         mpx:finish()
--     end
-- end)

function metapost.popformat()
    nofformats = nofformats - 1
end

function metapost.reset(mpx)
    if not mpx then
        -- nothing
    elseif type(mpx) == "string" then
        if mpxformats[mpx] then
            local instance = mpxformats[mpx]
            instance:finish()
            mpxterminals[mpx] = nil
            mpxextradata[mpx] = nil
            mpxformats  [mpx] = nil
        end
    else
        for name, instance in next, mpxformats do
            if instance == mpx then
                mpx:finish()
                mpxterminals[mpx] = nil
                mpxextradata[mpx] = nil
                mpxformats  [mpx] = nil
                break
            end
        end
    end
end

if not metapost.process then

    function metapost.process(specification)
        metapost.run(specification)
    end

end

-- run, process, convert and flush all work with a specification with the
-- following (often optional) fields
--
--     mpx          string or mp object
--     data         string or table of strings
--     flusher      table with flush methods
--     askedfig     string ("all" etc) or number
--     incontext    boolean
--     plugmode     boolean

do

    local function makebeginbanner(specification)
        return formatters["%% begin graphic: n=%s\n\n"](metapost.n)
    end

    local function makeendbanner(specification)
        return "\n% end graphic\n\n"
    end

    -- This is somewhat complex. We want a logger that is bound to an instance and
    -- we implement the rest elsewhere so we need some hook. When we decide to move
    -- the mlib-fio code here we can avoid some of the fuzzyness.

    -- In the luatex lib we have log and error an dterm fields, but here we don't
    -- because we handle that ourselves.

    -- mplib.realtimelogging = false

    local mp_tra   = { }
    local mp_tag   = 0

    local stack   = { }
    local logger  = false
    local logging = true

    local function pushlogger(mpx,tra)
        insert(stack,logger)
        logger = tra or false
    end

    local function poplogger(mpx)
        logger = remove(stack) or false
    end

    function metapost.checktracingonline(n)
        -- todo
    end

    function metapost.setlogging(state)
        logging = state
    end

    function metapost.newlogger()

        -- In a traditional scenario there are three states: terminal, log as well
        -- as both. The overhead of logging is large because metapost flushes each
        -- character (maybe that should be improved but caching at the libs end also
        -- has price, probably more than delegating to LUA).

        -- term=1 log=2 term+log =3

        local l, nl, dl = { }, 0, false

        return function(target,str)
            if not logging then
                return
            elseif target == 4 then
                report_error(str)
            else
                if logger and (target == 2 or target == 3) then
                    logger:write(str)
                end
                if target == 1 or target == 3 then
                    if str == "\n" then
                        mplib.realtimelogging = true
                        if nl > 0 then
                            report_tracer(concat(l,"",1,nl))
                            nl, dl = 0, false
                        elseif not dl then
                            report_tracer("")
                            dl = true
                        end
                    else
                        nl = nl + 1
                        l[nl] = str
                    end
                end
            end
        end

    end

    function metapost.run(specification)
        local mpx       = specification.mpx
        local data      = specification.data
        local converted = false
        local result    = { }
        local mpxdone   = type(mpx) == "string"
        if mpxdone then
            mpx = metapost.pushformat { instance = mpx, format = mpx }
        end
        if mpx and data then
            local tra = false
            starttiming(metapost) -- why not at the outer level ...
            metapost.variables = { } -- todo also push / pop
            metapost.pushscriptrunner(mpx)
            if trace_graphics then
                tra = mp_tra[mpx]
                if not tra then
                    mp_tag = mp_tag + 1
                    local jobname = tex.jobname
                    tra = {
                        inp = io.open(formatters["%s-mplib-run-%03i.mp"] (jobname,mp_tag),"w"),
                        log = io.open(formatters["%s-mplib-run-%03i.log"](jobname,mp_tag),"w"),
                    }
                    mp_tra[mpx] = tra
                end
                local banner = makebeginbanner(specification)
                tra.inp:write(banner)
                tra.log:write(banner)
                pushlogger(mpx,tra and tra.log)
            else
                pushlogger(mpx,false)
            end
            if trace_tracingall then
                executempx(mpx,"tracingall;")
            end
            --
            if data then
                if trace_graphics then
                    if type(data) == "table" then
                        for i=1,#data do
                            tra.inp:write(data[i])
                        end
                    else
                        tra.inp:write(data)
                    end
                end
                starttiming(metapost.exectime)
                result = executempx(mpx,data)
                stoptiming(metapost.exectime)
                if not metapost.reporterror(result) and result.fig then
                    converted = metapost.convert(specification,result)
                end
                if result and result.status > 2 and metapost.quitonerror then
                    luatex.abort()
                end
            else
                report_metapost("error: invalid graphic")
            end
            --
            if trace_graphics then
                local banner = makeendbanner(specification)
                tra.inp:write(banner)
                tra.log:write(banner)
            end
            stoptiming(metapost)
            poplogger()
            metapost.popscriptrunner()
        end
        if mpxdone then
            metapost.popformat()
        end
        return converted, result
    end

end

if not metapost.convert then

    function metapost.convert()
        report_metapost('warning: no converter set')
    end

end

function metapost.directrun(formatname,filename,outputformat,astable,mpdata)
    report_metapost("producing postscript and svg is no longer supported")
end

do

    local result = { }
    local width  = 0
    local height = 0
    local depth  = 0
    local bbox   = { 0, 0, 0, 0 }

    local flusher = {
        startfigure = function(n,llx,lly,urx,ury)
            result = { }
            width  = urx - llx
            height = ury
            depth  = -lly
            bbox   = { llx, lly, urx, ury }
        end,
        flushfigure = function(t)
            local r = #result
            for i=1,#t do
                r = r + 1
                result[r] = t[i]
            end
        end,
        stopfigure = function()
        end,
    }

    -- make table variant:

    function metapost.simple(instance,code,useextensions,dontwrap)
        -- can we pickup the instance ?
        local mpx = metapost.pushformat {
            instance = instance or "simplefun",
            format   = "metafun", -- or: minifun
            method   = "double",
        }
        metapost.process {
            mpx        = mpx,
            flusher    = flusher,
            askedfig   = 1,
            useplugins = useextensions,
            data       = dontwrap and { code } or { "beginfig(1);", code, "endfig;" },
            incontext  = false,
        }
        metapost.popformat()
        if result then
            local stream = concat(result," ")
            result = { } -- nil -- cleanup .. weird, we can have a dangling q
            return stream, width, height, depth, bbox
        else
            return "", 0, 0, 0, { 0, 0, 0, 0 }
        end
    end

end

local getstatistics = mplib.getstatistics

local function currentusage(mpx)
    return 0 -- todo
end

function metapost.getstatistics(memonly)
    if memonly then
        local n, m = 0, 0
        for name, mpx in next, mpxformats do
            local s = getstatistics(mpx)
            n = n + 1
            m = currentusage(mpx)
        end
        return n, m
    else
        local t = { }
        for name, mpx in next, mpxformats do
            t[name] = getstatistics(mpx)
        end
        return t
    end
end

local gethashentries = mplib.gethashentries

function metapost.gethashentries(name,full)
    if name then
        local mpx = mpxformats[name] or mpxformats[name .. ":1"]
        if mpx then
            return gethashentries(mpx,full)
        end
    else
        local t = { }
        for name, mpx in next, mpxformats do
            t[name] = gethashtokens(mpx,full)
        end
        return t
    end
end

local gethashtokens = mplib.gethashtokens

function metapost.getinstancenames()
    return sortedkeys(mpxformats)
end

function metapost.getinstancebyname(name)
    return mpxformats[name]
end

function metapost.getinstanceinfo(mpx)
    return mpxnames[mpx] or { name = "unknown", method = "unknown" }
end


-- This used to be in mlib-pdf but is also used elsewhere so we need to
-- define it early.

-- default_bend_tolerance 131/65536.0
-- default_move_tolerance 131/65536.0

-- local function curved(ith,pth,tolerance) --- still better than the build in
--     local d = pth.left_x - ith.right_x
--     local b = abs(ith.right_x - ith.x_coord - d)
--     if b <= tolerance then
--         b = abs(pth.x_coord - pth.left_x - d)
--         if b <= tolerance then
--             d = pth.left_y - ith.right_y
--             b = abs(ith.right_y - ith.y_coord - d)
--             if b <= tolerance then
--                 b = abs(pth.y_coord - pth.left_y - d)
--                 if b <= tolerance then
--                     return false
--                 end
--             end
--         end
--     end
--     return true
-- end

-- The final two tests are for the case when the control point lie on the other side
-- of the other point (so to say). One could use a different factor in front of the
-- parentheses, but I have not managed to find it (there are two control points, so
-- it might be complicated in the end). Here we only test if it is on the other side
-- of the other point. If so, we mark it as curve. Thus, if this is not the case,
-- then the control points lie between the points, and we should be safe.

local a1 = math.abs
local a2 = xmath.fabs

function metapost.hascurvature(ith,pth,tolerance)
-- if true then return true end
-- if true then return pth.curved end
    local v1x = ith.right_x - ith.x_coord
    local v1y = ith.right_y - ith.y_coord
    local v2x = pth.left_x - pth.x_coord
    local v2y = pth.left_y - pth.y_coord
    local eps = abs(v1x * v2y - v2x * v1y)
-- print("XYZ",eps,tolerance)
    if eps > tolerance then
-- print(1,pth.curved,true)
        return true
    end
    local v3x = pth.x_coord - ith.x_coord
    local v3y = pth.y_coord - ith.y_coord
    eps = abs(v3x * v2y - v2x * v3y)
    -- print("ZYX",eps,tolerance)
    if eps > tolerance then
-- print(2,pth.curved,true)
        return true
    end
    eps = abs(v3x * v1y - v1x * v3y)
    -- print("ABC",eps,tolerance)
    if eps > tolerance then
-- print(3,pth.curved,true)
        return true
    end
    --
    eps = v1x * v3x + v1y * v3y -- v1 \cdot v3 = |v1|*|v3|cos([v1,v3])
    if eps < 0 then
-- print(3,pth.curved,true)
        return true
    end
    eps = v2x * v3x + v2y * v3y -- v2 \cdot v3 = |v2|*|v3|cos([v2,v3])
    if eps > 0 then
-- print(4,pth.curved,true)
        return true
    end
    eps = (v1x * v1x + v1y * v1y) - (v3x * v3x + v3y * v3y) -- checking lengths
    if eps > 0 then
-- print(5,pth.curved,true)
        return true
    end
    eps = (v2x * v2x + v2y * v2y) - (v3x * v3x + v3y * v3y) -- checking lengths
    if eps > 0 then
-- print(6,pth.curved,true)
        return true
    end
    return false
end
