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

local next = next
local ceil, floor, random, sqrt, cos, sin, pi, max, min = math.ceil, math.floor, math.random, math.sqrt, math.cos, math.sin, math.pi, math.min, math.max
local remove = table.remove

-- Below is a bit of rainy saturday afternoon hobyism, while listening to Judith
-- Owens redisCOVERed (came there via Leland Sklar who I have on a few live blurays;
-- and who is also on YT). (Also nice: https://www.youtube.com/watch?v=GXqasIRaxlA)

-- When Aditya pointed me to an article on mazes I ended up at poison distributions
-- which to me looks nicer than what I normally do, fill a grid and then randomize
-- the resulting positions. With some hooks this can be used for interesting patterns
-- too. A few links:
--
-- https://bost.ocks.org/mike/algorithms/#maze-generation
-- https://extremelearning.com.au/
-- https://www.jasondavies.com/maps/random-points/
-- http://devmag.org.za/2009/05/03/poisson-disk-sampling

-- The next function is quite close to what us discribed in the poisson-disk-sampling
-- link mentioned before. One can either use a one dimensional grid array or a two
-- dimensional one. The example code uses some classes dealing with points. In the
-- process I added some more control.

-- we could do without the samplepoints list

local function poisson(width, height, mindist, newpointscount, initialx, initialy)
    local starttime       = os.clock()
    local cellsize        = mindist / sqrt(2)
    local nofwidth        = ceil(width // cellsize)
    local nofheight       = ceil(height // cellsize)
    local grid            = lua.newtable(nofwidth,0) -- table.setmetatableindex("table")
    local firstx          = initialx or random() * width
    local firsty          = initialy or random() * height
    local firstpoint      = { firstx, firsty, 1 }
 -- local samplepoints    = { firstpoint }
    local processlist     = { firstpoint }
    local nofprocesslist  = 1
    local nofsamplepoints = 1
    local twopi           = 2 * pi

    for i=1,nofwidth do
        local g = lua.newindex(nofheight,false)
        grid[i] = g
    end

    local x = floor(firstx // cellsize) + 1 -- lua indices
    local y = floor(firsty // cellsize) + 1 -- lua indices

    x = max(1, min(x, width  - 1))
    y = max(1, min(y, height - 1))

    grid[x][y] = firstpoint

    -- The website shows graphic for this 5*5 grid snippet, if we use a one dimentional
    -- array then we could have one loop; a first version used a metatable trick so we
    -- had grid[i+gx][j+gy] but we no we also return the grid, so ... we now just check.

    -- There is no need for the samplepoints list as we can get that from the grid but
    -- instead we can store the index with the grid.

    while nofprocesslist > 0 do
        local point = remove(processlist,random(1,nofprocesslist))
        nofprocesslist = nofprocesslist - 1
        for i=1,newpointscount do -- we start at 1
            local radius = mindist * (random() + 1)
            local angle  = twopi * random()
            local nx     = point[1] + radius * cos(angle)
            local ny     = point[2] + radius * sin(angle)
            if nx > 1 and ny > 1 and nx <= width and ny <= height then -- lua indices
                local gx = floor(nx // cellsize)
                local gy = floor(ny // cellsize)
                -- the 5x5 cells around the point
                for i=-2,2 do
                    for j=-2,2 do
                        local cell = grid[i + gx]
                        if cell then
                            cell = cell[j + gy]
                            if cell and sqrt((cell[1] - nx)^2 + (cell[2] - ny)^2) < mindist then
                                goto next
                            end
                        end
                    end
                end
             -- local newpoint  = { nx, ny }
                nofprocesslist  = nofprocesslist + 1
                nofsamplepoints = nofsamplepoints + 1
                local newpoint  = { nx, ny, nofsamplepoints }
                processlist [nofprocesslist]  = newpoint
             -- samplepoints[nofsamplepoints] = newpoint
                grid[gx][gy] = newpoint
            end
            ::next::
        end
    end

    return {
        count  = nofsamplepoints,
     -- points = samplepoints,
        grid   = grid,
        time   = os.clock() - starttime,
    }
end

-- For now:

local randomizers     = utilities.randomizers or { }
utilities.randomizers = randomizers
randomizers.poisson   = poisson

-- The MetaFun interface:

local formatters = string.formatters
local concat = table.concat

local f_macro = formatters["%s(%N,%N);"]

local f_macros = {
    [2] = formatters["%s(%N,%N);"],
    [3] = formatters["%s(%N,%N,%i);"],
    [4] = formatters["%s(%N,%N,%i,%i);"],
}

local function grid_to_mp(t,f,n)
    local grid   = t.grid
    local count  = t.count
    local result = { }
    local r      = 0
    local macro  = f or "draw"
    local runner = f_macros[n or 2] or f_macros[2]
    for i=1,#grid do
        local g = grid[i]
        if g then
            for j=1,#g do
                local v = g[j]
                if v then
                    r = r + 1
                    result[r] = runner(macro,v[1],v[2],v[3],count)
                end
            end
        end
    end
    return concat(result, " ")
end

local getparameter = metapost.getparameter

local function lmt_poisson()
    local initialx = getparameter { "initialx" }
    local initialy = getparameter { "initialy" }
    local width    = getparameter { "width" }
    local height   = getparameter { "height" }
    local distance = getparameter { "distance" }
    local count    = getparameter { "count" }

    local result = poisson (
        width, height, distance, count,
        initialx > 0 and initialx or false,
        initialy > 0 and initialy or false
    )

    if result then
        logs.report("poisson","w=%N, h=%N, d=%N, c=%N, n=%i, runtime %.3f",
            width, height, distance, count, result.count, result.time
        )
    end

    return result
end

function mp.lmt_poisson_generate()
    local result = lmt_poisson()
    if result then
        return grid_to_mp (
            result,
            getparameter { "macro" },
            getparameter { "arguments" }
        )
    end
end

-- -- some playing around showed no benefit
--
-- function points_to_mp(t,f)
--     local points = t.points
--     local count  = t.count
--     local result = { }
--     local r      = 0
--     local macro  = f or "draw"
--     local runner = f_macros[n or 2] or f_macros[2]
--     for i=1,count do
--         local v = points[i]
--         r = r + 1
--         result[r] = runner(macro,v[1],v[2],v[3],count)
--     end
--     return concat(result, " ")
-- end
--
-- local result  = false
-- local i, j, n = 0, 0, 0
--
-- function mp.lmt_poison_start()
--     result = lmt_poisson()
-- end
--
-- function mp.lmt_poisson_stop()
--     result = false
-- end
--
-- function mp.lmt_poisson_count()
--     return result and result.count or 0
-- end
--
-- function mp.lmt_poisson_get(i)
--     if result then
--         return mp.pair(result.points[i])
--     end
-- end
--
-- function mp.lmt_poisson_generate()
--     mp.lmt_poisson_start()
--     if result then
--         return grid_to_mp (
--             result,
--             getparameter { "macro" },
--             getparameter { "arguments" }
--         )
--     end
--     mp.lmt_poisson_stop()
-- end
