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

-- The meshing code evolved stepwise. A first idea was to do more at the \METAPOST\
-- end but in the end we settled for a regular \METAFUN\ plugin: we never manipulate
-- these paths if only because the fills and draws demand synchronization so we have
-- many small paths then. We used the (new) curvature trickery so performance was
-- actually quite okay.
--
-- We also played with overlap detection and splitting triangles in smaller ones but
-- this is error prone so in the end we just went for tracing, discarding and
-- retaining features. As with everything \TEX\ and \METAPOST\ it is better to have
-- full user control than to fight sub-optimal heuristics. So, code came and went.
--
-- Eventually also the granular mesh helpers in \METAPOST\ were ditched because we
-- won't use them anyway.

local type, next, tonumber = type, next, tonumber

local concat, sortedkeys, sortedhash, sort = table.concat, table.sortedkeys, table.sortedhash, table.sort
local format = string.format
local round = math.round

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

local report                = logs.reporter("metapost","mesh")

local formatters            = string.formatters

local injectpath            = mp.inject.path
local injectvectors         = mp.inject.vectors
local scanstring            = mp.scan.string -- whatever
local registerscript        = metapost.registerscript

local vectornew             = vector.new
local vectortruncate        = vector.truncate
local vectorgetepsilon      = vector.getepsilon
local vectortotable         = vector.totable
local vectorgetrow          = vector.getrow

local contour               = vector.contour
local mesh                  = vector.mesh

local contourgetmesh        = contour.getmesh
local contourgetarea        = contour.getarea
local contouraverage        = contour.average
local contoursort           = contour.sort
local contourmakemesh       = contour.makemesh
local contourcollectoverlap = contour.collectoverlap
local contourbounds         = contour.bounds

local meshtotable           = mesh.totable

local meshes  = { }
local nofruns = 0

-- contour.overlapmap = {
--     -- not intersecting:
--     [0x00] = "nop_bound",
--     [0x01] = "nop_plane_one",
--     [0x02] = "nop_plane_two",
--     [0x03] = "nop_coplanar",
--     [0x04] = "nop_final",
--     [0x0E] = "nop_same_points", -- user values
--     [0x0F] = "nop_same_values", -- user values
--     -- intersecting:
--     [0x10] = "yes_bound",
--     [0x11] = "yes_unused_1",
--     [0x12] = "yes_unused_2",
--     [0x13] = "yes_coplanar",
--     [0x14] = "yes_final",
-- }

local granularities = {
    triangle = 3, triangles = 3,
    quad     = 4, quads     = 4,
    line     = 2, lines     = 2,
    dot      = 1, dots      = 1,
    pixel    = 0, pixels    = 0,
}

function mp.mesh_register(specification)
    if type(specification) ~= "table" then
        report("%s, registry %a, no %s specified","registering","unknown","properties")
    else
        local parent = specification.parent
        local name   = specification.name or "unknown"
        if not specification.meshes then
            specification.meshes = specification.triangles or specification.quads
        end
        specification.granularity = tonumber(granularities[specification.granularity or "triangle"]) or 3
        if name == "unknown" then
            report("%s, registry %a, no %s specified","registering",name,"name")
        elseif not specification.points then
            report("%s, registry %a, no %s specified","registering",name,"points")
        elseif not specification.meshes then
            report("%s, registry %a, no %s specified","registering",name,"meshes")
        else
            meshes[name] = specification
        end
    end
end

statistics.register("metapost meshing", function()
    if nofruns > 0 then
        return format("%i runs, %s seconds",nofruns,elapsedtime(meshes))
    end
end)

-- extensions

local function contouroverlapping(overlap)
    if type(overlap) == "table" then
        local hash = { }
        for i=1,#overlap do
            local f  = overlap[i]
            local f1 = f[1]
            local f2 = f[2]
            hash[f1] = (hash[f1] or 0) + 1
            hash[f2] = (hash[f2] or 0) + 1
        end
        return next(hash) and hash or nil
    end
end

local methods = {
    "moller",
    "guigue and devillers",
}

local averages = {
    "mean",
    "minimum",
    "mean + minimum",
}


local function vectormeshname(name,method,average)
    return formatters
        ["mesh-%s-%i-%i.lua"]
        (name or "unknown",method or 1, average or 1)
end

vector.meshname = vectormeshname

local function contourshow(points,meshlist,found,current)
    if current.quad then
        return
    end
    local count    = found and #found or 0
    local method   = current.method
    local average  = current.average
    local epsilon  = current.epsilon or vectorgetepsilon(true)
    local smethod  = methods[method] or methods[1]
    local saverage = averages[average] or averages[1]
    report("")
    report("points    : %i",#points)
    report("meshes    : %i",#meshlist)
    report("overlaps  : %i",count)
    report("method    : %i (%s)",method,smethod)
    report("average   : %i (%s)",average,saverage)
    report("epsilon   : %f",epsilon)
    if count > 0 then
        report("")
        local hash  = contouroverlapping(found)
        local keys  = sortedkeys(hash)
        local areas = { }
        for k, v in sortedhash(hash) do
            local a = contourgetarea(points,meshlist,k)
            areas[k] = a
        --     print("hash",k,v,meshlist[k],a)
        end
        sort(keys,function(a,b) return areas[a] < areas[b] end)
        for i=1,#keys do
            local k = keys[i]
            report("% 5i  # %i  ∆ %0.9f",k,hash[k],areas[k])
        end
        report("")
        if current.save then
            local logname = vectormeshname(current.name,method,average)
            local loglist = {
                method    = { id = method,  name = smethod },
                average   = { id = average, name = saverage } ,
                points    = vectortotable(points),
                meshes    = meshtotable(meshlist),
                overlap   = found,
                areas     = areas,
                unique    = hash,
                N         = current.N or 0,
                M         = current.M or 0,
                state     = current.state,
                epsilon   = epsilon,
            }
            table.save(logname,loglist)
        end
    end
end

contour.overlapping = contouroverlapping
contour.show        = contourshow

---

-- local setbytemap  = mp.setbytemap
-- local getbytemap  = mp.getbytemap
-- local newbytemap  = mp.newbytemap
-- local fillbytemap = mp.fillbytemap

local function mesh_convert(current,kind)

    local state = current.substate or current.state

    local llx = state.minx
    local lly = state.miny
    local urx = state.maxx
    local ury = state.maxy

    local rx = 1 / (urx - llx)
    local ry = 1 / (ury - lly)
    local tx = -llx * rx
    local ty = -lly * ry

    local line        = current.linewidth or 0.01
    local color       = current.color     or function(v) return v, v, v end
    local points      = current.points
    local list        = current.list
    local granularity = current.granularity or 3
    local overlap     = current.overlap
    local keep        = current.keep
    local trace       = current.trace or 1
    local first       = current.first or 1
    local last        = current.last  or #list
    local trace       = current.trace
    local hash        = false
    local vmin        = current.vmin
    local vmax        = current.vmax
    local vdelta      = current.vdelta

    local t = { }
    local n = 0

    local k = { draw = "S", fill = "f", both = "B" }

    local fill     = false
    local operator = "S"

    if granularity == 3 or granularity == 4 then
        operator = k[kind] or k.both
        fill     = operator ~= "S"
    end

    if granularity == 3 and type(overlap) == "table" then
        hash = contouroverlapping(overlap)
        keep = keep and table.tohash(keep) or { }
    else
        overlap = false
        keep    = false
    end

    -- todo: we need to use the same pattern for %Z

    local f_shape_3 = formatters["%N %N m %N %N l %N %N l %N %N l h " .. operator]
    ----- f_shape_3 = formatters["%N %N m %N %N l %N %N l %1Z %2Z l h " .. operator]
    local f_shape_4 = formatters["%N %N m %N %N l %N %N l %N %N l %N %N l h " .. operator]
    ----- f_shape_4 = formatters["%N %N m %N %N l %N %N l %N %N l %1Z %2Z l h " .. operator]
    local f_shape_2 = formatters["%N %N m %N %N l " .. operator]
    local f_shape_1 = formatters["%N %N m %N %N l " .. operator]
    ----- f_shape_1 = formatters["%N %N m %1Z %2Z l " .. operator]
    local f_draw    = formatters["%.3N G"]
    local f_fill    = formatters["%.3N %.3N %.3N rg"]
    local f_tdraw   = formatters["%.3N %.3N %.3N RG"]
    local f_tfill   = formatters["%.3N g"]
    local f_line    = formatters["10 M 1 J 1 j %N w"]
    local f_width   = formatters["%N w"]
    local f_cm      = formatters["%N %N %N %N %N %N cm"]

    n = n + 1 ; t[n] = "q"
    n = n + 1 ; t[n] = f_draw(0) -- outlines
    n = n + 1 ; t[n] = f_line(line)
    n = n + 1 ; t[n] = f_cm(rx,0,0,ry,tx,ty)

    if granularity == 0 then

        for i=1,#points do
         -- if not hash or not hash[i] or keep[i] then
                local x, y, c, w = vectorgetrow(points,i)
                if vdelta ~= 0 then
                    c = (c - vmin) / vdelta -- TODO
                end
                n = n + 1 ; t[n] = f_tdraw(color(c,i))
                n = n + 1 ; t[n] = f_shape_1(x,y,x,y)
             -- n = n + 1 ; t[n] = f_shape_1(x,y)
         -- end
        end

    elseif granularity == 1 then

        local lastw = false
        for i=1,#points do
         -- if not hash or not hash[i] or keep[i] then
                local x, y, c, w = vectorgetrow(points,i)
                if vdelta ~= 0 then
                    c = (c - vmin) / vdelta -- TODO
                end
                n = n + 1 ; t[n] = f_tdraw(color(c,i))
                if w ~= lastw then
                    n = n + 1 ; t[n] = f_width(w)
                    lastw = w
                end
                n = n + 1 ; t[n] = f_shape_1(x,y,x,y)
             -- n = n + 1 ; t[n] = f_shape_1(x,y)
         -- end
        end

    elseif granularity == 2 then

        for i=first,last do
         -- if not hash or not hash[i] or keep[i] then
                local m, c = contourgetmesh(points,list,i)
                if m then
                    if vdelta ~= 0 then
                        c = (c - vmin) / vdelta -- TODO
                    end
                    n = n + 1 ; t[n] = f_tdraw(color(c,i))
                    n = n + 1 ; t[n] = f_shape_2(m[1],m[2],m[3],m[4])
                elseif m ~= false then
                    report("invalid mesh index %i, case %i",i,3)
                end
         -- end
        end

    elseif granularity == 3 then

        for i=first,last do
            if not hash or not hash[i] or keep[i] then
                local m, c = contourgetmesh(points,list,i)
                if m then
                    if vdelta ~= 0 then
                        c = (c - vmin) / vdelta -- TODO
                    end
                    if fill then
                        n = n + 1 ; t[n] = f_fill(color(c,i))
                    end
                    n = n + 1 ; t[n] = f_shape_3(m[1],m[2],m[3],m[4],m[5],m[6],m[1],m[2])
                 -- n = n + 1 ; t[n] = f_shape_3(m[1],m[2],m[3],m[4],m[5],m[6])
                elseif m ~= false then
                    report("invalid mesh index %i, case %i",i,3)
                end
            end
        end

    elseif granularity == 4 then

        for i=first,last do
         -- if not hash or not hash[i] or keep[i] then
                local m, c = contourgetmesh(points,list,i)
                if m then
                    if vdelta ~= 0 then
                        c = (c - vmin) / vdelta -- TODO
                    end
                    if fill then
                        n = n + 1 ; t[n] = f_fill(color(c,i))
                    end
                    n = n + 1 ; t[n] = f_shape_4(m[1],m[2],m[3],m[4],m[5],m[6],m[7],m[8],m[1],m[2])
                 -- n = n + 1 ; t[n] = f_shape_4(m[1],m[2],m[3],m[4],m[5],m[6],m[7],m[8])
                elseif m ~= false then
                    report("invalid mesh index %i, case %i",i,3)
                end
         -- end
        end

    end

    if trace and overlap then
        -- we fetch these meshes a few times but it's tracing so seldom used
        f_shape_3 = formatters["%N %N m %N %N l %N %N l %N %N l h S"]
     -- f_shape_3 = formatters["%N %N m %N %N l %N %N l %1Z %2Z l h S"]
        local factor = tonumber(current.factor) or 5
        -- first the ones with a single overlap
        n = n + 1 ; t[n] = f_line(2*factor*line)
        n = n + 1 ; t[n] = f_tdraw(0,.6,0)
        for k, v in sortedhash(hash) do
            if v == 1 then
                local m, c = contourgetmesh(points,list,k)
                if m then
                    n = n + 1 ; t[n] = f_shape_3(m[1],m[2],m[3],m[4],m[5],m[6],m[1],m[2])
                 -- n = n + 1 ; t[n] = f_shape_3(m[1],m[2],m[3],m[4],m[5],m[6])
                end
            end
        end
        -- then the ones with multiple overlaps
        n = n + 1 ; t[n] = f_line(factor*line)
        n = n + 1 ; t[n] = f_tdraw(.6,0,0)
        for k, v in sortedhash(hash) do
            if v > 1 then
                local m, c = contourgetmesh(points,list,k)
                if m then
                    n = n + 1 ; t[n] = f_shape_3(m[1],m[2],m[3],m[4],m[5],m[6],m[1],m[2])
                 -- n = n + 1 ; t[n] = f_shape_3(m[1],m[2],m[3],m[4],m[5],m[6])
                end
            end
        end
        -- finally we make sure lines are shown
        n = n + 1 ; t[n] = f_line(line)
        n = n + 1 ; t[n] = f_tdraw(1,1,1)
        for k, v in sortedhash(hash) do
            local m, c = contourgetmesh(points,list,k)
            if m then
                n = n + 1 ; t[n] = f_shape_3(m[1],m[2],m[3],m[4],m[5],m[6],m[1],m[2])
             -- n = n + 1 ; t[n] = f_shape_3(m[1],m[2],m[3],m[4],m[5],m[6])
            end
        end

        contourshow(points,list,overlap,current)

    end

    n = n + 1 ; t[n] = "Q"
    t = concat(t,"\n")

    return t, llx, lly, urx, ury
end

-- In the end we don't get what we want unless we snap the coordinates. Calculating
-- bitmap triangles is tricky. So when I'm in the mood I'll do a snap on grid first
-- and then continue the experiment. Needs discussion first. We anyway keep the
-- pixel variant (granularity 0).

-- local function mesh_bytemap(current)
--
--     local state = current.substate or current.state
--
--     local llx = state.minx
--     local lly = state.miny
--     local urx = state.maxx
--     local ury = state.maxy
--
--     local rx = 1 / (urx - llx)
--     local ry = 1 / (ury - lly)
--     local tx = -llx * rx
--     local ty = -lly * ry
--
--     local color   = current.color  or function(v) return v, v, v end
--     local points  = current.points
--     local list    = current.list
--     local vmin    = current.vmin
--     local vdelta  = current.vdelta
--
--     local bytemap = current.bytemap or 1
--     local nx      = round((urx - llx) * 100)
--     local ny      = round((ury - lly) * 100)
--
-- if xtrue then
--
-- --     local cmap = 20
-- --     local dmap = 21
--
-- --     -- if we go gray scale we cmap can be bytemap
--
-- --  -- local r, g, b = 200, 200, 200
-- --     local r, g, b = 255, 255, 255
--
-- --     newbytemap(bytemap,nx,ny,3)
-- --     fillbytemap(bytemap,r,g,b)
--
-- --     newbytemap(cmap,nx,ny,1)
-- --     newbytemap(dmap,nx,ny,1)
--
-- --     local function setbyte(x,y,c,r,g,b)
-- --         local d = getbytemap(dmap,x,y)
-- --         if d then
-- --             if d == 0 then
-- --                 setbytemap(dmap,x,y,1)
-- --                 -- set
-- --             elseif getbytemap(cmap,x,y) < c then
-- --                 -- set
-- --             else
-- --                 -- ignore
-- --                 goto next
-- --             end
-- --         end
-- --         setbytemap(bytemap,x,y,r,g,b)
-- --         setbytemap(cmap,x,y,c)
-- --       ::next::
-- --     end
--
-- --     local min, max = math.min, math.max
--        -- this pixel inside a trangle approach is not okay
-- --     local function triangle(m,c,r,g,b)
-- --         local x1 = round((m[1] - llx) * 100)
-- --         local y1 = round((m[2] - lly) * 100)
-- --         local x2 = round((m[3] - llx) * 100)
-- --         local y2 = round((m[4] - lly) * 100)
-- --         local x3 = round((m[5] - llx) * 100)
-- --         local y3 = round((m[6] - lly) * 100)
--
-- --         local xmin = min(x1,x2,x3)
-- --         local xmax = max(x1,x2,x3)
-- --         local ymin = min(y1,y2,y3)
-- --         local ymax = max(y1,y2,y3)
--
-- --         local x1x2 = x1 - x2
-- --         local x2x3 = x2 - x3
-- --         local x3x1 = x3 - x1
--
-- --         for y=ymin,ymax do
-- --             local d1 = x1x2 * (y - y1) ; local dd1 = y1 - y2
-- --             local d2 = x2x3 * (y - y2) ; local dd2 = y2 - y3
-- --             local d3 = x3x1 * (y - y3) ; local dd3 = y3 - y1
-- --             d1 = d1 + dd1 * x1
-- --             d2 = d2 + dd2 * x2
-- --             d3 = d3 + dd3 * x3
-- --             for x=xmin,xmax do
-- --                 if d1 > dd1 * x and d2 > dd2 * x and d3 > dd3 * x then
-- --                     setbyte(x,y,c,r,g,b)
-- --                 end
-- --             end
-- --         end
-- --     end
--
-- --     for i=1,#points do
-- --         local m, c = contourgetmesh(points,list,i)
-- --         if vdelta ~= 0 then
-- --             c = (c - vmin) / vdelta -- TODO
-- --         end
-- --         c = round(c * 255)
-- --         local r, g, b = color(c,i)
-- --         r = round(r * 255)
-- --         g = round(g * 255)
-- --         b = round(b * 255)
-- --         triangle(m,c,r,g,b)
-- --     end
--
-- elseif true then
--
--     -- why limited ? we need one outside mp
--
--     local cmap = 20
--     local dmap = 21
--
--     -- if we go gray scale we cmap can be bytemap
--
--  -- local r, g, b = 200, 200, 200
--     local r, g, b = 255, 255, 255
--
--     newbytemap(bytemap,nx,ny,3)
--     fillbytemap(bytemap,r,g,b)
--
--     newbytemap(cmap,nx,ny,1)
--     newbytemap(dmap,nx,ny,1)
--
--     for i=1,#points do
--         local x, y, c, w = vectorgetrow(points,i)
--         if vdelta ~= 0 then
--             c = (c - vmin) / vdelta -- TODO
--         end
--         x = round((x - llx) * 100)
--         y = round((y - lly) * 100)
--         c = round(c * 255)
--         local d = getbytemap(dmap,x,y)
--         if d then
--             if d == 0 then
--                 setbytemap(dmap,x,y,1)
--                 -- set
--             elseif getbytemap(cmap,x,y) < c then
--                 -- set
--             else
--                 -- ignore
--                 goto next
--             end
--         end
--         local r, g, b = color(c,i)
--         r = round(r * 255)
--         g = round(g * 255)
--         b = round(b * 255)
--         setbytemap(bytemap,x,y,r,g,b)
--         setbytemap(cmap,x,y,c)
--       ::next::
--     end
--
-- else
--
-- --     local base = 200 -- 255
--
-- --     newbytemap(bytemap,nx,ny,3)
-- --     fillbytemap(bytemap,base,base,base)
--
-- --     for i=1,#points do
-- --         local x, y, c, w = vectorgetrow(points,i)
-- --         if vdelta ~= 0 then
-- --             c = (c - vmin) / vdelta -- TODO
-- --         end
-- --         x = round((x - llx) * 100)
-- --         y = round((y - lly) * 100)
-- --         c = round(color(c,i) * 255)
-- --         local cc = getbytemap(bytemap,x,y)
-- --         if cc and (cc == base or cc < c) then
-- --             setbytemap(bytemap,x,y,c,128,128)
-- --          -- print("+",x,y,cc,c)
-- --         else
-- --          -- print("-",x,y,cc,c)
-- --         end
-- --     end
--
-- end
--
--     return bytemap, 0, nx, 0, ny
-- end

function metapost.mesh(name,kind)

    local current = meshes[name]

    -- messages

    if not current then
        return
    elseif not current.meshed then
        return
    end

    starttiming(meshes)

    if current then
        current.count = count
    end

    report("%s, registry %a, %i points, %i meshes","embedding",name or "unknown",#current.points,#current.list)

    local result = mesh_convert(current,kind or "both")

    stoptiming(meshes)

    return result
end

-- todo messages and timing

local function mesh_contour()

    nofruns = nofruns + 1

    local name    = scanstring()
    local kind    = scanstring()
    local current = meshes[name]

    if not current then

        local origin = { 0, 0 }

        injectpath { origin, origin, origin, origin, close = true }

        return
    end

    starttiming(meshes)

    if current.meshed == nil then

        current.method  = tonumber(current.method ) or 1
        current.average = tonumber(current.average) or 1

        local t = current.meshes
        local N = 0
        local M = 0
        if type(t) == "table" then
            N = tonumber(t.N or t[1]) or 0
            M = tonumber(t.M or t[2]) or N
        elseif type(t) == "number" then
            N = t
            M = t
        else
        end
        if N > 0 and M > 0 then
            current.list = contourmakemesh(N,M,current.granularity or 3)
        end
        current.N = N
        current.M = M

        -- todo: only when current.truncate == true

        vectortruncate(current.points,true,current.epsilon)

        local state = contouraverage(current.points,current.list,true,current.average) -- tolerant

        current.state  = state
        current.vmin   = state and state.minz or 0
        current.vmax   = state and state.maxz or 0
        current.vdelta = current.vmax - current.vmin

        contoursort(current.list)

        if current.overlap == true then
            current.overlap = contourcollectoverlap(current.points,current.list,false,current.method,current.epsilon)
        end

        current.meshed = true

    end

    report("%s, registry %a, %i points, %i meshes","processing",name or "unknown",#current.points,#current.list)

    local state = current.state or { minx = 0, miny = 0, maxx = 0, maxy = 0 }

    if current.first and current.last then
        state = contourbounds(current.points,current.list,current.first,current.last)
        current.substate = state
    end

    local bytemap, llx , lly, urx, ury

 -- if kind == "bytemap" then
 --
 --     bytemap, llx, lly, urx, ury = mesh_bytemap(current)
 --
 -- else

        llx = state.minx
        lly = state.miny
        urx = state.maxx
        ury = state.maxy

 -- end

    local bounds = {
        { llx, lly },
        { urx, lly },
        { urx, ury },
        { llx, ury },
        close = true,
    }

    injectpath(bounds)

    stoptiming(meshes)

end

registerscript("meshcontour", mesh_contour)

-- For now, as we have examples

vector.checkoverlap   = contour.checkoverlap
vector.collectoverlap = contour.collectoverlap
vector.triangulate    = contour.makemesh
vector.average        = contour.average
vector.sort           = contour.sort
vector.getmesh        = contour.getmesh

-- Maybe this should be a runtime module.
