if not modules then modules = { } end modules ['typo-syn'] = {
    version   = 1.000,
    optimize  = true,
    comment   = "companion to typo-syn.mkxl",
    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
    copyright = "PRAGMA ADE / ConTeXt Development Team",
    license   = "see context related readme files"
}

-- For the moment we have the splitter here but it actually belongs
-- in the builders namespace.

local nodes             = nodes

local tasks             = nodes.tasks
local enableaction      = tasks.enableaction
----- disableaction     = tasks.disableaction

local nuts              = nodes.nuts
local tonut             = nodes.tonut

local getattr           = nuts.getattr
local getattrlist       = nuts.getattrlist
local getdepth          = nuts.getdepth
local getdirection      = nuts.getdirection
local getdisc           = nuts.getdisc
local getglue           = nuts.getglue
local getheight         = nuts.getheight
local getid             = nuts.getid
local getlist           = nuts.getlist
local getnext           = nuts.getnext
local getprev           = nuts.getprev
local getprop           = nuts.getprop
local getsubtype        = nuts.getsubtype
local hasidsubtype      = nuts.hasidsubtype
local gettotal          = nuts.gettotal
local getwhd            = nuts.getwhd
local getwidth          = nuts.getwidth
local setattrlist       = nuts.setattrlist
local setdepth          = nuts.setdepth
local setdisc           = nuts.setdisc
local setheight         = nuts.setheight
local setlink           = nuts.setlink
local setlist           = nuts.setlist
local setnext           = nuts.setnext
local setoffsets        = nuts.setoffsets
local setprev           = nuts.setprev
local setprop           = nuts.setprop
local setwidth          = nuts.setwidth

local hpack             = nuts.hpack
local rangedimensions   = nuts.rangedimensions
local insertbefore      = nuts.insertbefore
local insertafter       = nuts.insertafter
local removenode        = nuts.remove
local flushnode         = nuts.flush

local traverselist      = nuts.traverselist

local nodecodes         = nodes.nodecodes

local glyph_code        <const> = nodecodes.glyph
local rule_code         <const> = nodecodes.rule
local dir_code          <const> = nodecodes.dir
local disc_code         <const> = nodecodes.disc
local hlist_code        <const> = nodecodes.hlist
local vlist_code        <const> = nodecodes.vlist
local math_code         <const> = nodecodes.math
local glue_code         <const> = nodecodes.glue
local kern_code         <const> = nodecodes.kern
local penalty_code      <const> = nodecodes.penalty

local line_code         <const> = nodes.listcodes.line
local fontkern_code     <const> = nodes.kerncodes.fontkern
local parfillskip_code  <const> = nodes.gluecodes.parfillrightskip
local baselineskip_code <const> = nodes.gluecodes.baselineskip

local new_direction     = nuts.pool.direction

local runningrule       <const> = tex.magicconstants.runningrule

-----------------

local synchronize       = typesetters.synchronize or { }
typesetters.synchronize = synchronize or { }

local registervalue     = attributes.registervalue
local getvalue          = attributes.getvalue
local hasvalues         = attributes.hasvalues

local a_synchronize     <const> = attributes.private("synchronize")

local trace  = false
-- local trace  = true
local report = logs.reporter("parallel")

local pushsavelevel = tex.pushsavelevel -- token.expandmacro("bgroup")
local popsavelevel  = tex.popsavelevel  -- token.expandmacro("egroup")
local dontcomplain  = tex.dontcomplain

local index    = 0
local lastattr = nil
local lastline = nil

interfaces.implement {
    name      = "registersynchronize",
    arguments = { "dimension", "dimension", "dimension", "box" },
    actions   = function(ht,dp,slack,box)
        index = index + 1
        box   = tonut(box)
        local t = {
            index      = index,
            lineheight = ht,
            linedepth  = dp,
            slack      = slack,
            height     = getheight(height),
            depth      = getheight(depth),
            box        = box,
        }
        local v = registervalue(a_synchronize,t)
        tex.setattribute(a_synchronize,v)
        if index > 0 then
            enableaction("vboxbuilders", "typesetters.synchronize.handler")
            enableaction("mvlbuilders",  "typesetters.synchronize.handler")
        end
    end,
}

-- When this is stable it can become a proper helper and primitive.

local function hsplit(box,targetwidth,targetheight,targetdepth,mcriterium,pcriterium,upto)
    local first     = getlist(box)
    local last      = first
    local current   = first
    local previous  = current
    local lastdisc  = nil
    local lastglyph = nil
 -- local stretch   = 0
 -- local shrink    = 0
    local width     = 0
    local height    = 0
    local depth     = 0
    local dirstack  = { } -- can move to outer
    local dirtop    = 0
    local minwidth  = targetwidth
    local maxwidth  = targetwidth
    local usedwidth = targetwidth
    while true do
        previous = current
        local id = getid(current)
        if id == glyph_code then
            local wd, ht, dp = getwhd(current)
            -- find next break first
            local newwidth = width + wd
            if newwidth >= usedwidth then
                if not lastdisc and lastglyph then
                    last = getprev(lastglyph)
                end
                break
            else
                width = newwidth
                if ht > height then
                    height = ht
                end
                if dp > depth then
                    depth = dp
                end
            end
            if not lastglyph then
                lastglyph = current
            end
        elseif id == kern_code then
            local wd = getwidth(current)
            local newwidth = width + wd
            if getsubtype(current) == fontkern_code then
                -- assume sane kerns
                width = newwidth
            else
                last = previous
                if newwidth >= usedwidth then
                    break
                else
                    width = newwidth
                end
                lastdisc  = nil
                lastglyph = nil
            end
        elseif id == disc_code then
            local pre, post, replace = getdisc(current)
            local wd = replace and rangedimensions(box,replace) or 0
            local newwidth = width + wd
            if newwidth >= usedwidth then
                break
            end
            local wd = pre and rangedimensions(box,pre) or 0
            local prewidth = width + wd
            if prewidth >= usedwidth then
                break
            end
            width    = newwidth
            lastdisc = current
        else
            -- common code at the end
            if id == glue_code or id == math_code then -- refactor : common code
                -- leaders
                last = previous
                local wd, more, less = getglue(current)
                local newwidth = width + wd
                if newwidth >= usedwidth then
                    break
                else
                    width   = newwidth
                 -- stretch = stretch + more
                 -- shrink  = shrink  + less
                    -- also for statistics
                    maxwidth = maxwidth + more
                    minwidth = minwidth + less
                    -- can become an option:
                    usedwidth = minwidth
                end
            elseif id == hlist_code or id == vlist_code then
                last = previous
                local wd, ht, dp = getwhd(current)
                local newwidth = width + wd
                if newwidth >= usedwidth then
                    break
                else
                    width = newwidth
                    if ht > height then
                        height = ht
                    end
                    if dp > depth then
                        depth = dp
                    end
                end
            elseif id == rule_code then
                last = previous
                local wd, ht, dp = getwhd(current)
                local newwidth = width + wd
                if newwidth >= usedwidth then
                    break
                else
                    width = newwidth
                    if ht ~= runningrule and ht > height then
                        height = ht
                    end
                    if dp ~= runningrule and dp > depth then
                        depth = dp
                    end
                end
            elseif id == dir_code then
                local dir, cancel = getdirection(current)
                if cancel then
                    if dirtop > 0 then
                        dirtop = dirtop - 1
                    end
                else
                    dirtop = dirtop + 1
                    dirstack[dirtop] = dir
                end
            end
            lastdisc  = nil
            lastglyph = nil
        end
        local next = getnext(current)
        if next then
            current = next
        else
            last = previous
            break
        end
    end
    local next
    if lastdisc then
        local pre, post, replace, pretail, posttail, replacetail = getdisc(lastdisc,true)
        last = getprev(lastdisc)
        --
        next = getnext(lastdisc)
        if next then
            setprev(next)
        end
        --
        setlink(last,pre)
        last = pretail
        if post then
            setlink(posttail,next)
            next = post
        end
        setdisc(lastdisc,nil,nil,replace)
        flushnode(lastdisc)
    else
        next = getnext(last)
        if next then
            setprev(next)
        end
        setnext(last)
    end
    while last do
        local id = getid(last)
        if id == glue_code or id == penalty_code then
         -- if id == glue_code then
         --     local wd, more, less = getglue(last)
         --  -- stretch = stretch - more
         --  -- shrink  = shrink  - less
         --     width   = width   - wd
         --     -- also for statistics
         --     maxwidth = maxwidth - more
         --     minwidth = minwidth - less
         --     -- can become an option:
         --     usedwidth = minwidth
         -- end
            first, last = removenode(first,last,true)
        else
            break
        end
    end
    if dirtop > 0 then
        for i=dirtop,1,-1 do
            local d = new_direction(dirstack[i],true)
            first, last = insertafter(first,last,d)
        end
        for i=1,dirtop do
            local d = new_direction(dirstack[i],false)
            next = insertbefore(next,next,d)
        end
    end
    if first then
     -- pushsavelevel()
     -- dontcomplain()
        local result
        if upto then
            result = hpack(first)
        else
            local badness, overshoot
            result, badness, overshoot = hpack(first,targetwidth,"exactly")
            local pdone = pcriterium and badness > pcriterium
            local mdone = mcriterium and badness > mcriterium
            if overshoot > 0 then
                if pdone then
                    result = hpack(first)
                end
            elseif overshoot < 0 then
                if mdone then
                    result = hpack(first)
                end
            else
                if pdone or mdone then
                    result = hpack(first)
                end
            end
        end
     -- popsavelevel()
        setattrlist(result,getattrlist(box)) -- useattrlist(result,box)
        setheight(result,targetheight or height)
        setdepth(result,targetdepth or depth)
        setlist(box,next)
        setwidth(box,rangedimensions(box,next))
        return result
    end
end

do

    local scanners      = tokens.scanners
    local scanword      = scanners.word
    local scaninteger   = scanners.integer
    local scandimension = scanners.dimension

    local tonode        = nuts.tonode
    local getbox        = nuts.getbox
    local setbox        = nuts.setbox

    local direct_value <const> = tokens.values.direct
    local none_value   <const> = tokens.values.none

    interfaces.implement {
        name      = "hsplit",
        protected = true,
        public    = true,
        usage     = "value",
        actions   = function(what)
            local n = scaninteger()
            local w = 0
            local h = false
            local d = false
            local pcriterium = false
            local mcriterium = false
            local upto = false
            while true do
                local key = scanword()
                if key == "to" then
                    upto = false
                    w = scandimension()
                elseif key == "upto" then
                    upto = true
                    w = scandimension()
                elseif key == "width" then
                    w = scandimension()
                elseif key == "height" then
                    h = scandimension()
                elseif key == "depth" then
                    d = scandimension()
                elseif key == "criterium" then
                    pcriterium = scaninteger()
                    mcriterium = scaninteger()
                elseif key == "shrinkcriterium" then
                    mcriterium = scaninteger()
                elseif key == "stretchcriterium" then
                    pcriterium = scaninteger()
                else
                    break
                end
            end
            pushsavelevel()
            dontcomplain()
            local r = hsplit(getbox(n),w,h,d,mcriterium,pcriterium,upto)
            popsavelevel()
            if r then
                if what == "value" then
                    return direct_value, r
                else
                    context(tonode(r))
                end
            else
                setbox(n)
                if what == "value" then
                    return none_value, nil
                end
            end
        end,
    }

end

local function getproperties(parent)
    local props = getprop(parent,"parallel")
    if not props then
        local w, h, d = getwhd(parent)
        props = {
            width  = w,
            height = h,
            depth  = d,
        }
        setprop(parent,"parallel",props)
    end
    return props
end

local function setproperties(parent,data,result,level,ctotal)
    local props  = getproperties(parent)
    local depth  = props.depth
    local height = props.height
    local delta  = data.linedepth - depth
    if delta > 0 then
        depth = data.linedepth
        setdepth(parent,depth)
        props.depth = depth
        local n = getnext(parent)
        if n and hasidsubtype(n,glue_code,baselineskip_code) then
            setwidth(n,getwidth(n) - delta)
        end
    end
--     if height < data.lineheight then
--         height = data.lineheight
--         setheight(parent,height)
--         props.height = height
--     end
    local offset = level * ctotal
    if props.depth + offset > depth then
        setdepth(parent,props.depth+offset)
    end
    setoffsets(result,0,-offset)
    setwidth(result,0)
end

local function flush(head,first,last,a,parent,nesting)
    if first and nesting == 0 then
        local data = getvalue(a_synchronize,a)
        local upto = getnext(last)
        if upto and getid(upto) == penalty_code then
            upto = getnext(upto)
        end
        if upto and hasidsubtype(upto,glue_code,parfillskip_code) then
            upto = getnext(upto)
        end
        local props = getproperties(parent)
        local width = rangedimensions(parent,first,upto)
        if width > props.width then
            width = props.width
        end
        local content = data.box
        local index   = data.index
        if not content then
            if trace then
                report("index %i, verdict %a",index,"done")
            end
        else
            local result = nil
            local cwidth = getwidth(content)
            local ctotal = gettotal(content)
            if cwidth <= width then
                if trace then
                    report("index %i, available %p, content %p, verdict %a",index,width,cwidth,"fit")
                end
                result   = content
                data.box = nil
            elseif cwidth > width then
                if trace then
                    report("index %i, available %p, content %p, verdict %a",index,width,cwidth,"overflow")
                end
                result   = hsplit(content,width-(data.slack or 0),nil,nil,200)
                lastattr = a
                lastline = parent
            else
                report("index %i, verdict %a",index,"weird")
            end
            if result then
                setproperties(parent,data,result,1,ctotal)
                head = insertbefore(head,first,result)
            end
        end
    end
    return head
end

local function lastflush(lastline,lastattr)
    local data = getvalue(a_synchronize,lastattr)
    if not data then
        return
    end
    local content = data.box
    if not content or getwidth(content) == 0 then
        return
    end
    local head = getlist(lastline)
    if not head then
        return
    end
    local first  = head
    local last   = nil
    local props  = getproperties(lastline)
    local width  = props.width
    local height = props.height
    local depth  = props.depth
    local level  = 1
    if depth < data.linedepth then
        depth = data.linedepth
        setdepth(lastline,depth)
    end
    if height < data.lineheight then
        height = data.lineheight
        setheight(lastline,height)
    end
    while true do
        local content = data.box
        local index   = data.index
        if content then
            local result = nil
            local total  = 0
            local cwidth = getwidth(content)
            local ctotal = gettotal(content)
            if cwidth <= width then
                if trace then
                    report("index %i, available %p, content %p, verdict %a",index,width,cwidth,"fit")
                end
                result = content
                data.box  = nil
            elseif cwidth > width then
                if trace then
                    report("index %i, available %p, content %p, verdict %a",index,width,cwidth,"overflow")
                end
                result = hsplit(content,width-(data.slack or 0),nil,nil,200)
            else
                report("index %i, verdict %a",index,"weird")
            end
            if result then
                level = level + 1
                setproperties(lastline,data,result,level,ctotal)
                head = insertbefore(head,first,result)
                setlist(lastline,head)
            else
                break
            end
        else
            break
        end
    end
end

local processranges = nuts.processranges

function synchronize.handler(head,where)
    if where == "hmodepar" and hasvalues(a_synchronize) then
        lastattr = nil
        lastline = nil
        for n, id, subtype in traverselist(head) do
            if subtype == line_code then
                lastattr = nil
                local list = getlist(n)
                local head = processranges(a_synchronize,flush,list,n)
                if head ~= list then
                    setlist(n,head)
                end
            end
        end
        if lastattr and lastline then
            lastflush(lastline,lastattr)
        end
    end
    return head
end

--

local settings_to_array  = utilities.parsers.settings_to_array
local get_buffer_content = buffers.getcontent
local splitlines         = string.splitlines

interfaces.implement {
    name      = "synchronizesteps",
    arguments = { {
        { "list" },
        { "split" },
        { "buffer" },
        { "text" },
    } },
    actions   = function(t)
        local split  = t.split -- not used yet
        local list   = t.list
        local buffer = t.buffer
        local text   = t.text
        local data   = false
        if buffer and buffer ~= "" then
            data = settings_to_array(buffer)
            if #data == 2 then
                for i=1,#data do
                    data[i] = splitlines(get_buffer_content(data[i]) or "")
                end
            else
                return
            end
        elseif text and text ~= "" then
            data = settings_to_array(text)
            if #data == 2 then
                for i=1,#data do
                    data[i] = settings_to_array(data[i])
                end
            else
                return
            end
        else
            return
        end
        if list and list ~= "" then
            list = settings_to_array(list)
        else
            list = { }
        end
        local done = data[1]
        local dtwo = data[2]
        if #done == #dtwo then
            local lone = list[1] or ""
            local ltwo = list[2] or ""
            for i=1,#done do
                context.dosplitsynchronize(lone,ltwo,done[i],dtwo[i])
            end
        else
            context.type("[different sizes in synchronize]")
        end
    end,
}
