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

-- for the moment (when testing) we use a penalty 1

local setmetatableindex = table.setmetatableindex

local nodecodes         = nodes.nodecodes
local listcodes         = nodes.listcodes

local boundary_code     <const> = nodecodes.boundary
local hlist_code        <const> = nodecodes.hlist
local vlist_code        <const> = nodecodes.vlist
local kern_code         <const> = nodecodes.kern
local penalty_code      <const> = nodecodes.penalty
local glue_code         <const> = nodecodes.glue

local line_code         <const> = listcodes.line
local ghost_code        <const> = listcodes.ghost
local middle_code       <const> = listcodes.middle
local wrapped_code      <const> = listcodes.wrapped
local mathpack_code     <const> = listcodes.mathpack
local construct_code    <const> = listcodes.construct
local alignment_code    <const> = listcodes.alignment
local row_code          <const> = listcodes.row
local fence_code        <const> = listcodes.fence

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

local getid             = nuts.getid
local hasidsubtype      = nuts.hasidsubtype
local getnext           = nuts.getnext
local getprev           = nuts.getprev
local getattr           = nuts.getattr
local getprop           = nuts.getprop
local getwidth          = nuts.getwidth
local getdata           = nuts.getdata
local getdepth          = nuts.getdepth
local getheight         = nuts.getheight
local getlist           = nuts.getlist
local setglue           = nuts.setglue
local setwhd            = nuts.setwhd
local setoffsets        = nuts.setoffsets
local getdimensions     = nuts.dimensions
local getnormalizedline = nuts.getnormalizedline
local getbox            = nuts.getbox
local addxoffset        = nuts.addxoffset
local setattrlist       = nuts.setattrlist
local rangedimensions   = nuts.rangedimensions

local nextglue          = nuts.traversers.glue
local nextlist          = nuts.traversers.list
local nextboundary      = nuts.traversers.boundary
local nextnode          = nuts.traversers.node

local insertafter       = nuts.insertafter
local insertbefore      = nuts.insertbefore
local newkern           = nuts.pool.kern
local newstrutrule      = nuts.pool.strutrule

local texsetdimen       = tex.setdimen
local texgetdimen       = tex.getdimen
local texsetcount       = tex.setcount
local texisdimen        = tex.isdimen
local texiscount        = tex.iscount

local stages            = { }
local initial           = { }

local b_mathalign  <const> = tex.boundaries.system("mathalign")
local b_mathinject <const> = tex.boundaries.system("mathinject")
local a_location   <const> = attributes.system("mathnumberlocation")

local c_strc_math_n_of_lines   <const> = texiscount("c_strc_math_n_of_lines")
local d_strc_math_max_right    <const> = texisdimen("d_strc_math_max_right")
local d_strc_math_first_right  <const> = texisdimen("d_strc_math_first_right")
local d_strc_math_last_right   <const> = texisdimen("d_strc_math_last_right")
local d_strc_math_max_left     <const> = texisdimen("d_strc_math_max_left")
local d_strc_math_first_left   <const> = texisdimen("d_strc_math_first_left")
local d_strc_math_last_left    <const> = texisdimen("d_strc_math_last_left")
local d_strc_math_first_height <const> = texisdimen("d_strc_math_first_height")
local d_strc_math_last_depth   <const> = texisdimen("d_strc_math_last_depth")
local d_strc_math_indent       <const> = texisdimen("d_strc_math_indent")

local d_strc_math_left_shift   <const> = texisdimen("d_strc_math_left_shift")
local d_strc_math_right_shift  <const> = texisdimen("d_strc_math_right_shift")

local report = logs.reporter("mathalign")

local trace  = false  trackers.register("math.align",function(v) trace = v end )

local function moveon(s)
    for n, id, subtype in nextnode, getnext(s) do
        s = n
        if id == kern_code then
            -- move on (s_2 case)
        elseif id == glue_code then
            -- move on
        elseif id == penalty_code then
            -- move on (untested)
        elseif id == hlist_code and subtype == ghost_code then
            -- move on
        else
            break
        end
    end
    return s
end

-- -- todo: skip over ghost, maybe penalty, maybe glues all in one loop
--
-- local n = getnext(s)
-- if n and getid(n) == kern_code then -- also needed
--     n = getnext(n)
-- end
-- while n and hasidsubtype(n,hlist_code,ghost_code) do
--     n = getnext(n)
-- end
-- while n and getid(n) == glue_code do
-- if n and getid(n) == glue_code then
--     n = getnext(n)
-- end

local getpenalty = nuts.getpenalty

stages[1] = function(specification,stage)
    local box      = getbox(specification.box)
    local head     = getlist(box)
    local align    = specification.alignstate
    local distance = specification.distance
    local found    = { }
    local max      = 0

-- local sh = tex.getdimen("strutht")
-- local sd = tex.getdimen("strutdp")
-- local ch = sh/6
-- local cd = sd/3
-- for s in nextlist, head do
--     local wd, ht, dp = nuts.getwhd(s)
--     local dh = ht - sh
--     local dd = dp - sd
--     local clipped = false
--     if dh <= ch then
--         ht = sh
--         clipped = true
--     end
--     if dd <= cd then
--         dp = sd
--         clipped = true
--     end
--     if clipped then
--         report("delta (%p,%p), clipped",dh,dd)
--         nuts.setwhd(s,wd,ht,dp)
--     else
--         report("delta (%p,%p), kept",dh,dd)
--     end
-- end

    for s in nextboundary, head do
        local data = getdata(s)
        if data == b_mathalign then
            local s = moveon(s)
            found[#found+1] = { s, 0, head }
--         elseif data == b_mathinject then
--             print("!!!!!!!")
        end
    end
    if #found > 0 then
        if found[1] then
            max = distance + getdimensions(head,found[1][1])
            found[1][2] = max
        end
        for i=2,#found do
            local f = found[i]
            local n = f[1]
            local p = n
            while p do
                if getid(p) == penalty_code and getpenalty(p) == -10000 then
                    local w = getdimensions(p,n)
                    local d = distance + w
                    f[2] = d
                    f[3] = p
                    if d > max then
                        max = d
                    end
                    break
                end
                p = getprev(p)
            end
        end
        for i=1,#found do
            local f = found[i]
            local w = f[2]
            local d = i == 1 and (max-w) or -w
            local k = newkern(d)
            local r = newstrutrule(0,2*65536,2*65536) -- hm, this adds height and depth !
            local s = moveon(f[3])
            if trace then
                report("row %i, width %p, delta %p",i,w,d)
            end
            setattrlist(r,head)
            setattrlist(k,head)
            insertbefore(head,s,r)
            insertafter(head,r,k)
        end
    end
    texsetdimen("global",d_strc_math_indent,max)
    if align == 2 then
        for n in nextglue, head do
            setglue(n,getwidth(n),0,0,0,0)
        end
    end
end

local function handlepacked(parent,plist,position)
    if plist then
        local current = plist
-- print("")
-- nuts.show(current)
-- print("")
        while current do
            if hasidsubtype(current,hlist_code,wrapped_code) then
                local packed = ((getattr(current,a_location) or 0) >> 8) & 0xF
-- print(packed,a_location,getattr(current,a_location))
-- packed = 1
                if packed > 0 then
                 -- print("")
                 -- nuts.show(current)
                 -- print("")
                    local linewidth, wrapwidth, leading, trailing
                    local wlist = getlist(current)
                    if wlist and hasidsubtype(wlist,hlist_code,fence_code) then
                        wlist = getlist(wlist) -- left=none demands this
                    end
                    for wrapped, id, subtype, list in nextlist, wlist do
                        if id == hlist_code and subtype == construct_code then -- mathpack_code
                            while getid(list) == vlist_code do
                                list = getlist(list)
                            end
                            for a, id, subtype, alist in nextlist, list do
                                if subtype == alignment_code then
                                    for cell, id, subtype, clist in nextlist, alist do
                                        for n, id, subtype in nextlist, clist do
                                            local p = getprop(n,"mathalignshift")
                                            if p == "right" then
                                                if not leading then
                                                    linewidth = getwidth(parent)
                                                    wrapwidth = getwidth(current)
                                                    leading   = rangedimensions(parent,plist,current)
                                                end
                                                local shift = rangedimensions(parent,getnext(wrapped))
                                                addxoffset(n,shift + linewidth - wrapwidth - leading - position)
                                            elseif p == "left" then
                                                if not leading then
                                                    linewidth = getwidth(parent)
                                                    wrapwidth = getwidth(current)
                                                    leading   = rangedimensions(parent,plist,current)
                                                end
                                                local shift = nuts.rangedimensions(parent,wlist,wrapped)
                                                addxoffset(n,- shift - leading - position)
                                            end
                                        end
                                    end
                                end
                            end
                        end
                     -- wrapped = getnext(wrapped) -- some left-over bad in 5.5 anyway
                    end
                end
            end
            current = getnext(current)
        end
    end
end

local function reposition(n,offset)
    -- We need to relocate the local boxes that we use to push something left or
    -- right ... quite horrible and one needs a bit twisted mindset for this.
    for n, id, subtype, list in nextlist, getlist(n) do
        if subtype == middle_code then
            addxoffset(n,-offset)
        end
    end
    -- this is tricky ... see line numbering so it might become a real shift
    -- inside the box: setprops(n,"repositioned",true)
    addxoffset(n,offset)
end

stages[2] = function(specification,stage)
    local box         = getbox(specification.box)
    local head        = getlist(box)
    local align       = specification.alignstate
    local cnt         = 0
    local maxwidth    = false
    local firstwidth  = 0
    local lastwidth   = 0
    local maxright    = false
    local firstright  = false
    local lastright   = false
    local maxleft     = false
    local firstleft   = false
    local lastleft    = false
    local firstheight = 0
    local lastdepth   = 0
    local linenumber  = 0
    local leftmargin  = specification.leftmargin
    local rightmargin = specification.rightmargin
    local injections  = { }
    if trace then
        report("stage 2")
    end
    for n, id, subtype, list in nextlist, head do
        if subtype == line_code then
            local t = getnormalizedline(n)
         -- local m = t.rightskip + t.parfillrightskip
            local l = t.leftskip  + t.lefthangskip  + t.parinitleftskip  + t.parfillleftskip  + t.indent
            local r = t.rightskip + t.righthangskip + t.parinitrightskip + t.parfillrightskip

            for s in nextboundary, list do
                local data = getdata(s)
                if data == b_mathinject then
                    injections[n] = true
                    goto HERE
                end
            end

            linenumber = linenumber + 1

            local w = getwidth(n)
            local m = r
            if trace then
                report("line %i, width %p, left %p, right %p, used %p",linenumber,w,l,r,w-l-r)
            end

            if not maxleft or m > maxleft then
                maxleft = l
            end
            if not maxright or m > maxright then
                maxright = r
            end
            if not firstleft then
                firstleft = l
            end
            if not firstright then
                firstright = r
            end
            lastleft  = l
            lastright = r
            if not maxwidth then
                maxwidth    = m
                firstheight = getheight(n)
                firstwidth  = m
            elseif m < maxwidth then
                maxwidth = m
            end
            cnt       = cnt + 1
            lastwidth = m
            lastdepth = getdepth(n)
          ::HERE::
        end
    end
    if not maxwidth then
        maxwidth   = getwidth(box) -- safeguard
        firstleft  = 0
        lastleft   = 0
        maxleft    = 0
        firstright = 0
        lastright  = 0
        maxright   = 0
    end
    local position = 0
    if align == 1 then -- flushleft
        if trace then
            report("reposition %p, %s",0, "flush left")
            -- todo
        end
     -- position = leftmargin
    elseif align == 2 then -- middle
        position = (maxwidth-rightmargin)/2
        if trace then
            report("reposition %p, %s",position, "center")
        end
    elseif align == 3 then -- flushright
        position = maxwidth - rightmargin -- a nasty interplay with the tex end (TODO !!!)
        if trace then
            report("reposition %p, %s",maxwidth, "flush right")
        end
    end
    if stage == 2 and position ~= 0 then
        for n, id, subtype, list in nextlist, head do
            reposition(n,position)
        end
        firstleft  = firstleft  + position
        lastleft   = lastleft   + position
        maxleft    = maxleft    + position
        firstright = firstright - position
        lastright  = lastright  - position
        maxright   = maxright   - position
    else
        position = 0
    end
    local leftshift  = math.min(firstleft,lastleft,maxleft) - texgetdimen(d_strc_math_indent)
    local rightshift = false
    for n, id, subtype, list in nextlist, head do
        if subtype == line_code then
            local t = getnormalizedline(n)
            local l = t.leftskip  + t.lefthangskip  + t.parinitleftskip  + t.parfillleftskip  + t.indent
            local r = t.rightskip + t.righthangskip + t.parinitrightskip + t.parfillrightskip
            if not rightshift then rightshift = r elseif r < rightshift then rightshift = r end
            if injections[n] then
                setoffsets(n,-l,nil)
            end
            goto DONE
        end
        handlepacked(n,list,position)
      ::DONE::
    end
    rightshift = (rightshift or 0) - position
    --
    texsetcount("global",c_strc_math_n_of_lines,cnt)
    texsetdimen("global",d_strc_math_first_height,firstheight)
    texsetdimen("global",d_strc_math_last_depth,lastdepth)
    texsetdimen("global",d_strc_math_first_left,firstleft)
    texsetdimen("global",d_strc_math_first_right,firstright)
    texsetdimen("global",d_strc_math_last_left,lastleft)
    texsetdimen("global",d_strc_math_last_right,lastright)
    texsetdimen("global",d_strc_math_max_left,maxleft)
    texsetdimen("global",d_strc_math_max_right,maxright)
    --
    texsetdimen("global",d_strc_math_left_shift,leftshift or 0)
    texsetdimen("global",d_strc_math_right_shift,rightshift or 0)
end

stages[3] = stages[2]

stages[4] = function(specification,stage)
    local box = getbox(specification.box)
    nuts.openup(specification,getlist(box))
    local w, h, d = getdimensions(getlist(box),true) -- vertical
    setwhd(box,w,h,d)
end

interfaces.implement {
    name      = "handlemathhang",
    arguments = {
        {
            { "stage", "integer" },
         -- { "method" },
            { "alignstate", "integer" },
            { "box", "integer" },
            { "distance", "dimension" },
            { "inbetween", "dimension" },
            { "height", "dimension" },
            { "depth", "dimension" },
            { "leftmargin", "dimension" },
            { "rightmargin", "dimension" },
            { "snapped", "boolean"},
        }
    },
    actions   = function(specification)
        local stage = specification.stage
        if stage == 1 then
            initial = specification
        else
            setmetatableindex(specification,initial)
        end
        if stage > 0 and stage <= #stages then
            stages[stage](specification,stage)
        end
    end
}
