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

local report_hyphenation = logs.reporter("languages","hyphenation")

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

local lpegmatch     = lpeg.match

local tex           = tex
local context       = context
local nodes         = nodes

local implement     = interfaces.implement

local ctx_doifelse  = commands.doifelse

local nodecodes     = nodes.nodecodes

local disc_code         <const> = nodecodes.disc
local hlist_code        <const> = nodecodes.hlist
local vlist_code        <const> = nodecodes.vlist
local glue_code         <const> = nodecodes.glue
local penalty_code      <const> = nodecodes.penalty
local glyph_code        <const> = nodecodes.glyph
local par_code          <const> = nodecodes.par
local rule_code         <const> = nodecodes.rule

local indentlist_code   <const> = nodes.listcodes.indent
local line_code         <const> = nodes.listcodes.line
local balanceslot_code  <const> = nodes.listcodes.balanceslot
----- spacing_code      <const> = nodes.listcodes.spacing

local spacing_code      <const> = nodes.rulecodes.spacing

local topskip_code      <const> = nodes.gluecodes.topskip
local indentskip_code   <const> = nodes.gluecodes.indentskip
local userskip_code     <const> = nodes.gluecodes.userskip
local baselineskip_code <const> = nodes.gluecodes.baselineskip

local hmode_code        <const> = tex.modelevels.horizontal

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

local getnext       = nuts.getnext
local getprev       = nuts.getprev
local getboth       = nuts.getboth
local getdisc       = nuts.getdisc
local getid         = nuts.getid
local getsubtype    = nuts.getsubtype
local getidsubtype  = nuts.getidsubtype
local getlist       = nuts.getlist
local getattribute  = nuts.getattribute
local getbox        = nuts.getbox
local getdirection  = nuts.getdirection
local getwidth      = nuts.getwidth
local getheight     = nuts.getheight
local getdepth      = nuts.getdepth
local getwhd        = nuts.getwhd
local takebox       = nuts.takebox

local setlink       = nuts.setlink
local setboth       = nuts.setboth
local setnext       = nuts.setnext
local setprev       = nuts.setprev
local setbox        = nuts.setbox
local setlist       = nuts.setlist
local setdisc       = nuts.setdisc
local setwidth      = nuts.setwidth
local setheight     = nuts.setheight
local setdepth      = nuts.setdepth
local setshift      = nuts.setshift
local setsplit      = nuts.setsplit
local setattrlist   = nuts.setattrlist
local setwhd        = nuts.setwhd
local setglue       = nuts.setglue
local setsubtype    = nuts.setsubtype

local flushnode     = nuts.flushnode
local flushlist     = nuts.flushlist
local copynode      = nuts.copy
local copylist      = nuts.copylist
local findtail      = nuts.tail
local getdimensions = nuts.dimensions
local getwidth      = nuts.getwidth
local hpack         = nuts.hpack
local vpack         = nuts.vpack
local traverseid    = nuts.traverseid
----- traverse      = nuts.traverse
local free          = nuts.free
local findtail      = nuts.tail
local reverse       = nuts.reverse
local effectiveglue = nuts.effectiveglue

local insertbefore  = nuts.insertbefore
local replacenode   = nuts.replace
local insertafter   = nuts.insertafter

local nextdisc      = nuts.traversers.disc
local nextdir       = nuts.traversers.dir
local nexthlist     = nuts.traversers.hlist

local countnodes    = nuts.count
----- traverselist  = nuts.traverselist

local listtoutf     = nodes.listtoutf

local nodepool      = nuts.pool
local new_penalty   = nodepool.penalty
local new_spacing   = nodepool.spacingrule
local new_hlist     = nodepool.hlist
local new_glue      = nodepool.glue

local setlistcolor  = nodes.tracers.colors.setlist

local texget        = tex.get
local texgetbox     = tex.getbox
local texisdimen    = tex.isdimen
local texsetdimen   = tex.setdimen
local texgetnest    = tex.getnest

local values             = tokens.values
local dimension_value    <const> = values.dimension
local cardinal_value     <const> = values.cardinal
local direct_value       <const> = values.direct
local conditional_value  <const> = values.conditional
local boolean_value      <const> = values.boolean

local d_lastnaturalboxwd <const> = texisdimen("lastnaturalboxwd")
local d_lastnaturalboxht <const> = texisdimen("lastnaturalboxht")
local d_lastnaturalboxdp <const> = texisdimen("lastnaturalboxdp")

local d_givenwidth       <const> = texisdimen("givenwidth")
local d_givenheight      <const> = texisdimen("givenheight")
local d_givendepth       <const> = texisdimen("givendepth")

local function hyphenatedlist(head,usecolor)
    local current = head and tonut(head)
    while current do
        local id = getid(current)
        local prev, next = getboth(current)
        if id == disc_code then
            local pre, post, replace = getdisc(current)
            if not usecolor then
                -- nothing fancy done
            elseif pre and post then
                setlistcolor(pre,"darkmagenta")
                setlistcolor(post,"darkcyan")
            elseif pre then
                setlistcolor(pre,"darkyellow")
            elseif post then
                setlistcolor(post,"darkyellow")
            end
            if replace then
                flushlist(replace)
            end
            setdisc(current)
            if pre then
                setlink(prev,new_penalty(10000),pre)
                setlink(findtail(pre),current)
            end
            if post then
                setlink(current,new_penalty(10000),post)
                setlink(findtail(post),next)
            end
        elseif id == vlist_code or id == hlist_code then
            hyphenatedlist(getlist(current))
        end
        current = next
    end
end

implement {
    name      = "hyphenatedlist",
    arguments = { "integer", "boolean" },
    actions   = function(n,color)
        local b = texgetbox(n)
        if b then
            hyphenatedlist(b.list,color)
        end
    end
}

local function checkedlist(list)
    if type(list) == "number" then
        return getlist(getbox(tonut(list)))
    else
        return tonut(list)
    end
end

implement {
    name      = "showhyphenatedinlist",
    arguments = "integer",
    actions   = function(n)
        -- we just hyphenate (as we pass a hpack) .. a bit too much casting but ...
        local l = languages.hyphenators.handler(checkedlist(n))
        report_hyphenation("show: %s",listtoutf(l,false,true))
    end
}

do

    local function applytochars(current,doaction,noaction,nested)
        while current do
            local id = getid(current)
            if nested and (id == hlist_code or id == vlist_code) then
                context.beginhbox()
                applytochars(getlist(current),doaction,noaction,nested)
                context.endhbox()
            elseif id ~= glyph_code then
                noaction(tonode(copynode(current)))
            else
                doaction(tonode(copynode(current)))
            end
            current = getnext(current)
        end
    end

    local function applytowords(current,doaction,noaction,nested)
        local start
        while current do
            local id = getid(current)
            if id == glue_code then
                if start then
                    doaction(tonode(copylist(start,current)))
                    start = nil
                end
                noaction(tonode(copynode(current)))
            elseif nested and (id == hlist_code or id == vlist_code) then
                context.beginhbox()
                applytowords(getlist(current),doaction,noaction,nested)
                context.egroup()
            elseif not start then
                start = current
            end
            current = getnext(current)
        end
        if start then
            doaction(tonode(copylist(start)))
        end
    end

    local methods = {
        char       = applytochars,
        characters = applytochars,
        word       = applytowords,
        words      = applytowords,
    }

    implement {
        name      = "applytobox",
        arguments = {
            {
                { "box", "integer" },
                { "command" },
                { "method" },
                { "nested", "boolean" },
            }
        },
        actions   = function(specification)
            local list   = checkedlist(specification.box)
            local action = methods[specification.method or "char"]
            if list and action then
                action(list,context[specification.command or "ruledhbox"],context,specification.nested)
            end
         end
    }

    local split_char = lpeg.Ct(lpeg.C(1)^0)
    local split_word = lpeg.tsplitat(lpeg.patterns.space)
    local split_line = lpeg.tsplitat(lpeg.patterns.eol)

    local function processsplit(specification)
        local str     = specification.data    or ""
        local command = specification.command or "ruledhbox"
        local method  = specification.method  or "word"
        local spaced  = specification.spaced
        if command then
            command = context[command]
        end
        if method == "char" or method == "character" then
            if spaced then
                spaced = context.space
            end
            local words = lpegmatch(split_char,str)
            for i=1,#words do
                local word = words[i]
                if word == " " then
                    if spaced then
                        spaced()
                    end
                elseif command then
                    command(word)
                else
                    context(word)
                end
            end
        elseif method == "word" then
            if spaced then
                spaced = context.space
            end
            local words = lpegmatch(split_word,str)
            for i=1,#words do
                local word = words[i]
                if spaced and i > 1 then
                    spaced()
                end
                if command then
                    command(word)
                else
                    context(word)
                end
            end
        elseif method == "line" then
            if spaced then
                spaced = context.par
            end
            local words = lpegmatch(split_line,str)
            for i=1,#words do
                local word = words[i]
                if spaced and i > 1 then
                    spaced()
                end
                if command then
                    command(word)
                else
                    context(word)
                end
            end
        else
            context(str)
        end
    end

    implement {
        name      = "processsplit",
        actions   = processsplit,
        arguments = {
            {
                { "data" },
                { "command" },
                { "method" },
                { "spaced", "boolean" },
            }
        }
    }

end

do

    local a_vboxtohboxseparator <const> = attributes.private("vboxtohboxseparator")

    implement {
        name      = "vboxlisttohbox",
        arguments = { "integer", "integer", "dimension" },
        actions   = function(original,target,inbetween)
            local current = getlist(getbox(original))
            local head = nil
            local tail = nil
            while current do
                local id   = getid(current)
                local next = getnext(current)
                if id == hlist_code then
                    local list = getlist(current)
                    if head then
                        if inbetween > 0 then
                            local n = new_glue(0,0,inbetween)
                            setlink(tail,n)
                            tail = n
                        end
                        setlink(tail,list)
                    else
                        head = list
                    end
                    tail = findtail(list)
                    -- remove last separator
                    if getid(tail) == hlist_code and getattribute(tail,a_vboxtohboxseparator) == 1 then
                        local temp = tail
                        local prev = getprev(tail)
                        if next then
                            local list = getlist(tail)
                            setlink(prev,list)
                            setlist(tail)
                            tail = findtail(list)
                        else
                            tail = prev
                        end
                        flushnode(temp)
                    end
                    -- done
                    setnext(tail)
                    setlist(current)
                end
                current = next
            end
            local result = new_hlist()
            setlist(result,head)
            setbox(target,result)
         -- setbox(target,new_hlist(head))
        end
    }

    implement {
        name      = "hboxtovbox",
        arguments = "integer",
        actions   = function(n)
            local b = getbox(n)
            local factor = texget("baselineskip",false) / texget("hsize")
            setdepth(b,0)
            setheight(b,getwidth(b) * factor)
        end
    }

end

implement {
    name      = "boxtostring",
    arguments = "integer",
    public    = true,
    actions   = function(n)
        local b = texgetbox(n)
        local l = b and b.list
        if l then
            -- helper is defined later
            local s = nodes.toutf(l)
            if s then
                context.verbatim(s)
            end
        end
    end
}

do

    -- we can now move this to the tex end

    local function getnaturaldimensions(n)
        local w = 0
        local h = 0
        local d = 0
        local l = getlist(getbox(n))
        if l then
            w, h, d = getdimensions(l)
        end
        texsetdimen(d_lastnaturalboxwd,w)
        texsetdimen(d_lastnaturalboxht,h)
        texsetdimen(d_lastnaturalboxdp,d)
        return w, h, d
    end

    local function setboxtonaturalwd(n)
        local old = takebox(n)
        local new = hpack(getlist(old))
        setlist(old,nil)
        flushnode(old)
        setbox(n,new)
    end

    implement {
        name      = "getnaturaldimensions",
        arguments = "integer",
        public    = true,
        protected = true,
        untraced  = true,
        actions   = getnaturaldimensions
    }

    implement {
        name      = "naturalwd",
        arguments = "integer",
        usage     = "value",
        public    = true,
        protected = true,
        actions   = function(n)
            return dimension_value, (getnaturaldimensions(n))
        end
    }

    implement {
        name      = "getnaturalwd",
        arguments = "integer",
        usage     = "value",
        public    = true,
        protected = true,
        actions   = function(n)
            local l = getlist(getbox(n))
            return dimension_value, l and getdimensions(l) or 0
        end
    }

    implement {
        name      = "setnaturalwd",
        arguments = "integer",
        public    = true,
        protected = true,
        untraced  = true,
        actions   = setboxtonaturalwd
    }

    nodes.setboxtonaturalwd = setboxtonaturalwd

end

do

    local lefttoright_code <const> = tex.directioncodes.lefttoright
    ----- righttoleft_code <const> = tex.directioncodes.righttoleft

    local getfirstdirectioninlist  = nuts.getfirstdirectioninlist

    local function firstdirinbox(n)
        local b = getbox(n)
        if b then
            local l = getlist(b)
            if l then
                return getfirstdirectioninlist(l)
            end
        end
        return lefttoright_code
    end

    nodes.firstdirinbox = firstdirinbox

    implement {
        name      = "doifelserighttoleftinbox",
        arguments = "integer",
        actions   = function(n)
            ctx_doifelse(firstdirinbox(n) == righttoleft_code)
        end
    }

end

-- new (handy for mp) .. might move to its own module

do

    ----- tonut           = nodes.tonut
    ----- takebox         = nuts.takebox
    ----- flushlist       = nuts.flushlist
    ----- copylist        = nuts.copylist
    ----- getwhd          = nuts.getwhd
    ----- setbox          = nuts.setbox
    ----- new_hlist       = nuts.pool.hlist

    local boxes           = { }
    nodes.boxes           = boxes
    local cache           = table.setmetatableindex("table")
    local report          = logs.reporter("boxes","cache")
    local trace           = false

    trackers.register("nodes.boxes",function(v) trace = v end)

    function boxes.save(category,name,b)
        name = tonumber(name) or name
        local b = takebox(b)
        if trace then
            report("category %a, name %a, %s (%s)",category,name,"save",b and "content" or "empty")
        end
        if name == "+" then
            name = #cache[category] + 1
        end
        cache[category][name] = b or false
    end

    function boxes.savenode(category,name,n)
        name = tonumber(name) or name
        if trace then
            report("category %a, name %a, %s (%s)",category,name,"save",n and "content" or "empty")
        end
        cache[category][name] = tonut(n) or false
    end

    function boxes.found(category,name)
        name = tonumber(name) or name
        return cache[category][name] and true or false
    end

    function boxes.count(category)
        return #cache[category]
    end

    function boxes.direct(category,name,copy)
        name = tonumber(name) or name
        local c = cache[category]
        local b = c[name]
-- if name == "+" then
--         b = remove(c, 1)
-- else
--         b = c[name]
-- end
        if not b then
            -- do nothing, maybe trace
        elseif copy then
            b = copylist(b)
        else
            c[name] = false
        end
        if trace then
            report("category %a, name %a, %s (%s)",category,name,"direct",b and "content" or "empty")
        end
        if b then
            return tonode(b)
        end
    end

    function boxes.restore(category,name,box,copy)
        name = tonumber(name) or name
        local c = cache[category]
        local b = takebox(box)
        if b then
            flushlist(b)
        end
        local b = c[name]
        if not b then
            -- do nothing, maybe trace
        elseif copy then
            b = copylist(b)
        else
            c[name] = false
        end
        if trace then
            report("category %a, name %a, %s (%s)",category,name,"restore",b and "content" or "empty")
        end
        setbox(box,b or nil)
    end

    function boxes.prune(category)
        -- this one assumes an indexed list
        local c = cache[category]
        local t = { }
        local n = 0
        for i=1,#c do
            local ci = c[i]
            if ci then
                n = n + 1 ; t[n] = ci
            end
        end
        cache[category] = t
    end

    local function dimensions(category,name)
        name = tonumber(name) or name
        local b = cache[category][name]
        if b then
            return getwhd(b)
        else
            return 0, 0, 0
        end
    end

    boxes.dimensions = dimensions

    function boxes.reset(category,name)
        name = tonumber(name) or name
        local c = cache[category]
        if name and name ~= "" then
            local b = c[name]
            if b then
                flushlist(b)
                c[name] = false
            end
            if trace then
                report("category %a, name %a, reset",category,name)
            end
        else
            for k, b in next, c do
                if b then
                    flushlist(b)
                end
            end
            cache[category] = { }
            if trace then
                report("category %a, reset",category)
            end
        end
    end

    function boxes.dispose(category)
        boxes.reset(category)
        cache[category] = nil -- or false (for tracing)
    end

    implement {
        name      = "putboxincache",
        public    = true,
        protected = true,
        arguments = { "string", "string", "integer" },
        actions   = boxes.save,
    }

    implement {
        name      = "getboxfromcache",
        public    = true,
        protected = true,
        arguments = { "string", "string", "integer" },
        actions   = boxes.restore,
    }

    implement {
        name      = "directboxfromcache",
        public    = true,
        protected = true,
        arguments = "2 strings",
        actions   = { boxes.direct, context },
     -- actions   = function(category,name) local b = boxes.direct(category,name) if b then context(b) end end,
    }

    implement {
        name      = "directcopyboxfromcache",
        public    = true,
        protected = true,
        arguments = { "string", "string", true },
        actions   = { boxes.direct, context },
     -- actions   = function(category,name) local b = boxes.direct(category,name,true) if b then context(b) end end,
    }

    implement {
        name      = "copyboxfromcache",
        public    = true,
        protected = true,
        arguments = { "string", "string", "integer", true },
        actions   = boxes.restore,
    }

    implement {
        name      = "doifelseboxincache",
        public    = true,
        protected = true,
        arguments = "2 strings",
        actions   = { boxes.found, ctx_doifelse },
    }

    implement {
        name      = "resetboxesincache",
        public    = true,
        protected = true,
        arguments = "string",
        actions   = boxes.reset,
    }

    implement {
        name      = "pruneboxesincache",
        public    = true,
        protected = true,
        arguments = "string",
        actions   = boxes.prune,
    }

    implement {
        name      = "disposeboxesincache",
        public    = true,
        protected = true,
        arguments = "string",
        actions   = boxes.dispose,
    }

    -- we can share this ...

    implement {
        name      = "getboxcountfromcache",
        public    = true,
        protected = true,
        usage     = "value",
        arguments = "string",
        actions   = function(category)
            return cardinal_value, #cache[category]
        end,
    }

    implement {
        name      = "getboxwdfromcache",
        public    = true,
        protected = true,
        usage     = "value",
        arguments = "2 strings",
        actions   = function(category,name)
            local w, h, d = dimensions(category,name)
            return dimension_value, w
        end,
    }

    implement {
        name      = "getboxhtfromcache",
        arguments = "2 strings",
        public    = true,
        protected = true,
        usage     = "value",
        actions   = function(category,name)
            local w, h, d = dimensions(category,name)
            return dimension_value, h
        end,
    }

    implement {
        name      = "getboxdpfromcache",
        arguments = "2 strings",
        public    = true,
        protected = true,
        usage     = "value",
        actions   = function(category,name)
            local w, h, d = dimensions(category,name)
            return dimension_value, d
        end,
    }

    implement {
        name      = "getboxhtdpfromcache",
        arguments = "2 strings",
        public    = true,
        protected = true,
        usage     = "value",
        actions   = function(category,name)
            local w, h, d = dimensions(category,name)
            return dimension_value, h + d
        end,
    }

    implement {
        name      = "putnextboxincache",
        public    = true,
        protected = true,
        arguments = { "string", "string", "box" },
        actions   = function(category,name,b)
            name = tonumber(name) or name
            b = tonut(b)
            if trace then
                report("category %a, name %a, %s (%s)",category,name,"save",b and "content" or "empty")
            end
            cache[category][name] = b or false
        end
    }

end

-- implement {
--     name      = "shiftbox",
--     arguments = { "integer", "dimension" },
--     actions   = function(n,d)
--         setshift(getbox(n),d)
--     end,
-- }

implement { name = "vpackbox", arguments = "integer", actions = function(n) setbox(n,(vpack(takebox(n)))) end }
implement { name = "hpackbox", arguments = "integer", actions = function(n) setbox(n,(hpack(takebox(n)))) end }

implement { name = "vpackedbox", arguments = "integer", actions = function(n) context(vpack(takebox(n))) end }
implement { name = "hpackedbox", arguments = "integer", actions = function(n) context(hpack(takebox(n))) end }

implement {
    name      = "scangivendimensions",
    public    = true,
    protected = true,
    arguments = {
        {
            { "width",  "dimension" },
            { "height", "dimension" },
            { "depth",  "dimension" },
        },
    },
    actions   = function(t)
        texsetdimen(d_givenwidth, t.width  or 0)
        texsetdimen(d_givenheight,t.height or 0)
        texsetdimen(d_givendepth, t.depth  or 0)
    end,
}

local function stripglue(list)
    local done  = false
    local first = list
    while first do
        local id = getid(first)
        if id == glue_code or id == penalty_code then
            first = getnext(first)
        else
            break
        end
    end
    if first and first ~= list then
        -- we have discardables .. is this split okay? do we leak
        setsplit(getprev(first),first)
        flushlist(list)
        list = first
        done = true
    end
    if list then
        local tail = findtail(list)
        local last = tail
        while last do
            local id = getid(last)
            if id == glue_code or id == penalty_code then
                last = getprev(last)
            else
                break
            end
        end
        if last ~= tail then
            -- we have discardables
            flushlist(getnext(last))
            setnext(last)
            done = true
        end
    end
    return list, done
end

local function limitate(t) -- don't pack the result !
    local text = t.text
    if text then
        text = tonut(text)
    else
        return
    end
    local sentinel = t.sentinel
    if sentinel then
        sentinel = tonut(sentinel)
        local s = getlist(sentinel)
        setlist(sentinel)
        free(sentinel)
        sentinel = s
    else
        return tonode(text)
    end
    local width = getwidth(text)
    local list  = getlist(text)
    local done  = false
    if t.strip then
        list, done = stripglue(list)
        if not list then
            setlist(text)
            setwidth(text,0)
            return tonode(text)
        elseif done then
            width = getdimensions(list)
            setlist(text,list)
        end
    end
    local left  = t.left or 0
    local right = t.right or 0
    local total = left + right
    if total < width then
        local last     = nil
        local first    = nil
        local maxleft  = left
        local maxright = right
        local swidth   = getwidth(sentinel)
        if maxright > 0 then
            maxleft  = maxleft  - swidth/2
            maxright = maxright - swidth/2
        else
            maxleft  = maxleft  - swidth
        end
        for n in traverseid(glue_code,list) do
            local width = getdimensions(list,n)
            if width > maxleft then
                if not last then
                    last = n
                end
                break
            else
                last = n
            end
        end
        if last and maxright > 0 then
            for n in traverseid(glue_code,last) do
                local width = getdimensions(n)
                if width < maxright then
                    first = n
                    break
                else
                    first = n
                end
            end
        end
        if last then
            local rest = getnext(last)
            if rest then
                local tail = findtail(sentinel)
                if first and getid(first) == glue_code and getid(tail) == glue_code then
                    setwidth(first,0)
                end
                if last and getid(last) == glue_code and getid(sentinel) == glue_code then
                    setwidth(last,0)
                end
                if first and first ~= last then
                    local prev = getprev(first)
                    if prev then
                        setnext(prev)
                    end
                    setlink(tail,first)
                end
                setlink(last,sentinel)
                setprev(rest)
                flushlist(rest)
            end
        end
    end
    setlist(text)
    free(text)

    if t.freeze then
        local l = hpack(list,total,"exactly")
        for n in traverseid(glue_code,list) do
            setglue(n,(effectiveglue(n,l)))
        end
        setlist(l)
        flushnode(l)
    end
    return tonode(list)
end

implement {
    name      = "limitated",
    public    = true,
    protected = true,
    arguments = {
        {
            { "left",     "dimension" },
            { "right",    "dimension" },
            { "text",     "hbox" },
            { "sentinel", "hbox" },
            { "strip",    "boolean" },
            { "freeze",   "boolean" },
        }
    },
    actions   = function(t)
        context.dontleavehmode()
        context(limitate(t))
    end,
}

-- only in lmtx:

do

    local naturalhsize = nuts.naturalhsize

    implement {
        name      = "widthuptohere",
        public    = true,
        usage     = "value",
        actions   = function()
            local n = texgetnest()
            return dimension_value, n.mode == hmode_code and naturalhsize(getnext(tonut(n.head))) or 0
        end,
    }

end

implement {
    name      = "doifelseindented",
    public    = true,
    protected = true,
    actions   = function()
        local n = texgetnest()
        local b = false
        -- can be a helper !
        if n.mode == hmode_code then
            n = tonut(n.head)
            while n do
                n = getnext(n)
                if n then
                    local id, subtype = getidsubtype(n)
                    if id == glue_code then
                        if subtype == indentskip_code then
                            b = getwidth(n) > 0
                            break
                        end
                    elseif id == hlist_code then
                        if subtype == indentlist_code then
                            b = getwidth(n) > 0
                        end
                        break
                    elseif id == par_code then
                        -- okay
                    else
                        break
                    end
                end
            end
        end
        ctx_doifelse(b)
    end,
}

implement {
    name      = "noflinesinbox",
    public    = true,
    usage     = "value",
    arguments = "integer",
    actions   = function(n)
        local c = 0
        local b = getbox(n)
        if b then
            b = getlist(b)
            if b then
                c = countnodes(hlist_code,b) + countnodes(vlist_code,b)
            end
        end
        return cardinal_value, c
    end,
}

implement {
    name      = "gluetolinesinbox",
    public    = true,
    protected = false,
    arguments = { "integer", "dimension", "dimension", "dimension" },
    actions   = function(n,wd,ht,dp)
        -- maybe only inbetween
        local box = getbox(n)
        if box then
            local head = getlist(box)
            local current = head
            while current do
                local id, subtype = getidsubtype(current)
                if id == glue_code and subtype == userskip_code then
                 -- local line = new_hlist()
                 -- setwhd(line,wd,ht,dp) -- width for tracing
                 -- setsubtype(line,spacing_code)
                    local line = new_spacing(wd,ht,dp) -- rule
-- nuts.setoptions(line,16) -- discardable
                    setattrlist(line,current)
                    head          = insertbefore(head,current,new_glue(baselineskip_code))
                    head, current = replacenode(head,current,line)
                    head, current = insertafter(head,current,new_glue(baselineskip_code))
                end
                current = getnext(current)
            end
            setlist(box,head)
        end
    end,
}

implement {
    name      = "ifgluelineinbox",
    public    = true,
    usage     = "condition",
    arguments = "integer",
    actions   = function(n)
        local box   = getbox(n)
        local found = false
        if box and getsubtype(box) == balanceslot_code then
            local current = getlist(box)
            while current do
                local id, subtype = getidsubtype(current)
             -- if id == hlist_code and subtype == spacing_code then
                if id == rule_code and subtype == spacing_code then
                    found = true
                    break
                end
                current = getnext(current)
            end
        end
        return boolean_value, found
    end,
}

do

    local takebox = tex.takebox

    implement {
        name      = "thebox",
        public    = true,
        arguments = "integer",
        actions   = function(n)
            context(takebox(n))
        end
    }

end

-- only in lmtx

implement {
    name      = "reversevboxcontent",
    protected = true,
    public    = true,
    arguments = "integer",
    actions   = function(n)
        local b = getbox(n)
        local l = b and getid(b) == vlist_code and getlist(b)
        if l and getnext(l) then
            setlist(b,reverse(l)) -- no re-vpack here!
        end
    end
}

-- only in lmtx

do

    local scaninteger        = tokens.scanners.integer
    local scanbox            = tokens.scanners.box
    local scandimension      = tokens.scanners.dimension

    ----- setsubtype         = nuts.setsubtype
    local removenode         = nuts.remove
    local getnormalizedline  = nuts.getnormalizedline -- we can optimize this
    local getdimensions      = nuts.dimensions
    local getrangedimensions = nuts.rangedimensions
    local getwidth           = nuts.getwidth
    local getheight          = nuts.getheight
    local getdepth           = nuts.getdepth

    local setprop            = nuts.setprop
    local getprop            = nuts.getprop

    local line_code          <const> = nodes.listcodes.line
    local alignment_code     <const> = nodes.listcodes.alignment
    local unknown_code       <const> = nodes.listcodes.unknown

    -- todo: make helper that formats

    local reporterror = logs.texerrormessage

    -- The first variant did a linear lookup but for large boxes and lots of
    -- analysis that is not nice. Okay, in practice performance is quite ok
    -- (milliseconds for thousands of access) but still ... the next is nicer
    -- and it's part of the experimental fun stuff anyway.

    local function countlines(box)
        local prop = getprop(box,"boxlines")
        if not prop then
            local line = 0
            local list = getlist(box)
-- node.show(tonode(box),100000,100000)
            prop = { }
            if list then
                for n, subtype in nexthlist, list do
                    if subtype == line_code or subtype == alignment_code then
                        line = line + 1
                        prop[line] = n
-- local h, f, l = nuts.migrate(n,true)
-- if f then
--     nuts.setpost(n,f)
-- end
                    end
                end
            end
            setprop(box,"boxlines",prop)
        end
        return prop
    end

    local function boxlinecount(what)
        local n   = scaninteger()
        local box = getbox(n)
        if what == "value" then
            return cardinal_value, box and #countlines(box) or 0
        end
    end

    local function boxlinemaxwhd(what,get)
        local n   = scaninteger()
        local box = getbox(n)
        if box and what == "value" then
            local prop = getprop(box,"boxlines")
            if not prop then
                prop = countlines(box)
            end
            local v = 0
            if prop then
                for i=1,#prop do
                    local w = get(prop[i])
                    if w > v then
                        v = w
                    end
                end
            end
            return dimension_value, v
        end
    end

    local function boxlinemaxwd(what) return boxlinemaxwhd(what,getwidth)  end
    local function boxlinemaxht(what) return boxlinemaxwhd(what,getheight) end
    local function boxlinemaxdp(what) return boxlinemaxwhd(what,getdepth)  end

    local function findline()
        local n    = scaninteger()
        local line = scaninteger()
        local box  = getbox(n)
        if box then
            local prop = getprop(box,"boxlines")
            if not prop then
                prop = countlines(box)
            end
            local found = prop[line]
            if found then
                local props = getprop(found,"lineproperties") or { } -- alignment has no properties (yet)
                if not props then
                    props = getnormalizedline(found)
                    props.width, props.height, props.depth = getwhd(found)
                    setprop(found,"lineproperties",props)
                end
                return props, line, found, box
            end
        end
        reporterror("no line %i in box %i",line,n)
    end

    local function findrange()
        local n     = scaninteger()
        local first = scaninteger()
        local last  = scaninteger()
        local box   = getbox(n)
        if box then
            local prop = getprop(box,"boxlines")
            if not prop then
                prop = countlines(box)
            end
            if first > 0 and last <= #prop then
                for i = first, last do
                    local found = prop[i]
                    local props = getprop(found,"lineproperties")
                    if not props then
                        props = getnormalizedline(found) or { } -- alignment has no properties (yet)
                        props.width, props.height, props.depth = getwhd(found)
                        setprop(found,"lineproperties",props)
                    end
                end
                return prop, first, last, box
            end
        end
        reporterror("no lines %i - %i in box %i",first,last,n)
    end

    local function getline(props,line,found,box,value)
        local p, n = getboth(found)
        local temp = new_hlist()
        setsubtype(temp,getsubtype(found))
        setwhd(temp,getwhd(found))
        if found == getlist(box) then
            setlink(temp,n)
            setlist(box,temp)
        else
            setlink(p,temp,n)
        end
        getprop(box,"boxlines")[line] = temp
        setboth(found)
        setsubtype(found, unknown_code)
        if value then
            return direct_value, found
        else
            context(tonode(found))
        end
    end

    local function copyline(props,line,found,box,value)
        found = copynode(found)
        setsubtype(found, unknown_code)
        if value then
            return direct_value, found
        else
            context(tonode(found))
        end
    end

    local function setline(props,line,found,box)
        local p, n = getboth(found)
        local temp = scanbox()
        if temp then
            temp = tonut(temp)
            if found == getlist(box) then
                setlink(temp,n)
                setlist(box,temp)
            else
                setlink(p,temp,n)
            end
            flushnode(found)
            getprop(box,"boxlines")[line] = temp
       end
    end

    local function naturaldimensions(p,l,found)
        if not p.naturalwidth then
            p.naturalwidth, p.naturalheight, p.naturaldepth = getdimensions(getlist(found))
        end
        return p
    end

    local function rangedimensions(p,f,l,box)
        local w, h, d = getrangedimensions(box,p[f],getnext(p[l]),true)
        return { width = w, height = h, depth = d }
    end

    local getters_one = {

        ["wd"]  = function(p,l,found) return dimension_value, p.width end,
        ["ht"]  = function(p,l,found) return dimension_value, p.height end,
        ["dp"]  = function(p,l,found) return dimension_value, p.depth end,
        ["ls"]  = function(p,l,found) return dimension_value, p.leftskip end,
        ["rs"]  = function(p,l,found) return dimension_value, p.rightskip end,
        ["lh"]  = function(p,l,found) return dimension_value, p.lefthangskip end,
        ["rh"]  = function(p,l,found) return dimension_value, p.righthangskip end,
        ["lp"]  = function(p,l,found) return dimension_value, p.parfillleftskip end,
        ["rp"]  = function(p,l,found) return dimension_value, p.parfillrightskip end,
        ["il"]  = function(p,l,found) return dimension_value, p.parinitleftskip end,
        ["ir"]  = function(p,l,found) return dimension_value, p.parinitrightskip end,
        ["in"]  = function(p,l,found) return dimension_value, p.indent end,

        ["nw"]  = function(p,l,found) return dimension_value, naturaldimensions(p,l,found).naturalwidth end,
        ["nh"]  = function(p,l,found) return dimension_value, naturaldimensions(p,l,found).naturalheigth end,
        ["nd"]  = function(p,l,found) return dimension_value, naturaldimensions(p,l,found).naturaldepth end,

        ["get"] = function(p,l,found,box) return getline(p,l,found,box,true) end,
    }

    local getters_two = {
        ["wd"] = function(p,f,l,box) return dimension_value, rangedimensions(p,f,l,box).width  end,
        ["ht"] = function(p,f,l,box) return dimension_value, rangedimensions(p,f,l,box).height end,
        ["dp"] = function(p,f,l,box) return dimension_value, rangedimensions(p,f,l,box).depth  end,
    }

    local setters_one = {
        ["wd"]   = function(p,l,found) return setwidth (found,scandimension(false,false,true)) end,
        ["ht"]   = function(p,l,found) return setheight(found,scandimension(false,false,true)) end,
        ["dp"]   = function(p,l,found) return setdepth (found,scandimension(false,false,true)) end,
        ["set"]  = setline,
        ["get"]  = getline,
        ["copy"] = copyline,
    }

    local function boxline(name,what)
        local props, line, found, box = findline()
        if not found then
            --
        elseif what == "value" then
            local getter = getters_one[name]
            if getter then
                return getter(props,line,found,box)
            end
        else
            local setter = setters_one[name]
            if setter then
                return setter(props,line,found,box)
            end
        end
    end

    --
    local function boxrange(name,what)
        local prop, first, last, box = findrange()
        if not prop then
            --
        elseif what == "value" then
            local getter = getters_two[name]
            if getter then
                return getter(prop,first,last,box)
            end
        else
            local setter = setters_two[name]
            if setter then
                return setter(prop,first,last,box)
            end
        end
    end

    local function define_one(name,action)
        implement {
            name    = name,
            public  = true,
            usage   = "value",
            actions = function(what) return boxline(action,what) end,
        }
    end

    local function define_two(name,action)
        implement {
            name    = name,
            public  = true,
            usage   = "value",
            actions = function(what) return boxrange(action,what) end,
        }
    end

    implement {
        name    = "boxlines",
        public  = true,
        usage   = "value",
        actions = boxlinecount,
    }

    implement { name = "boxlinemaxwd", public = true, usage = "value", actions = boxlinemaxwd }
    implement { name = "boxlinemaxht", public = true, usage = "value", actions = boxlinemaxht }
    implement { name = "boxlinemaxdp", public = true, usage = "value", actions = boxlinemaxdp }

    -- todoL: just inline so that s-system-macros can find them

    define_one("boxline",     "get")
    define_one("setboxline",  "set")
    define_one("copyboxline", "copy")
    define_one("boxlineht",   "ht")
    define_one("boxlinedp",   "dp")
    define_one("boxlinewd",   "wd")
    define_one("boxlinels",   "ls")
    define_one("boxliners",   "rs")
    define_one("boxlinelh",   "lh")
    define_one("boxlinerh",   "rh")
    define_one("boxlinelp",   "lp")
    define_one("boxlinerp",   "rp")
    define_one("boxlineil",   "il")
    define_one("boxlineir",   "ir")
    define_one("boxlinein",   "in")
    define_one("boxlinenw",   "nw")
    define_one("boxlinenh",   "nh")
    define_one("boxlinend",   "nd")

    define_two("boxrangewd",  "wd")
    define_two("boxrangeht",  "ht")
    define_two("boxrangedp",  "dp")

end

do

    local getbox   = tex.getbox
    local setfield = nodes.setfield
    local getfield = nodes.getfield
    local flush    = nodes.flushnode
    local copynode = nodes.copy

    local function get(n,field,copy)
        local b = getbox(n)
        if b then
            local p = getfield(b,field)
            if copy then
                p = copynode(p)
            else
                setfield(b,field)
            end
            context(p) -- todo: proper return
        end
    end

    local function set(n,l,field)
        local b = getbox(n)
        if b then
            setfield(b,field,l)
        else
            flush(l)
        end
    end

    implement {
        name      = "prelistbox",
        public    = true,
        usage     = "value",
        arguments = { "integer", '"pre"' },
        actions   = get,
    }

    implement {
        name      = "postlistbox",
        public    = true,
        usage     = "value",
        arguments = { "integer", '"post"' },
        actions   = get,
    }

    implement {
        name      = "prelistcopy",
        public    = true,
        usage     = "value",
        arguments = { "integer", '"pre"', true },
        actions   = get,
    }

    implement {
        name      = "postlistcopy",
        public    = true,
        usage     = "value",
        arguments = { "integer", '"post"', true },
        actions   = get,
    }

    implement {
        name      = "setprelistbox",
        public    = true,
        usage     = "value",
        arguments = { "integer", "box", '"pre"' },
        actions   = set,
    }

    implement {
        name      = "setpostlistbox",
        public    = true,
        usage     = "value",
        arguments = { "integer", "box", '"post"' },
        actions   = set,
    }

end

do

    local function setsplitlisthtdp(n,ht,dp)
        local box = getbox(n)
        if box then
            local head = getlist(box)
            if head then
                local tail = findtail(head)
                local id = getid(head)
                if id == glue_code and getsubtype(head) == topskip_code then
                    head = getnext(head)
                    id   = head and getid(head)
                    ht   = ht - getwidth(head)
                end
                if id == hlist_code and getsubtype(head) == line_code and getheight(head) < ht then
-- print("set height",ht)
                    setheight(head,ht)
                end
                local id = getid(tail)
                if id == glue_code then
                    tail = getprev(tail)
                    id   = tail and getid(tail)
                    dp   = dp - getwidth(tail)
                end
                if id == hlist_code and getsubtype(tail) == line_code and getdepth(tail) < dp then
                    setdepth(dept,dp)
-- print("set depth",dp)
                end
            end
        end
    end

    implement {
        name      = "setsplitlisthtdp",
        public    = true,
        protected = true,
        arguments = { "integer", "dimension", "dimension" },
        actions   = setsplitlisthtdp,
    }
end

do

    local ctx_validtext = context.core.validtext

    implement {
        name    = "iftext",
        public  = true,
        usage   = "condition",
        actions = function()
            -- this is kind of weird, the valid_text does the iftrue / iffalse
            ctx_validtext()
            return conditional_value
        end
    }

    implement {
        name      = "ifboxhaslist",
        public    = true,
        usage     = "condition",
        arguments = "integer",
        actions   = function(n)
            local b = texgetbox(n)
            return boolean_value, b and b.list and true or false
        end
    }

end
