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

--[[

This table mechanism is a combination between TeX and Lua. We do process
cells at the TeX end and inspect them at the Lua end. After some analysis
we have a second pass using the calculated widths, and if needed cells
will go through a third pass to get the heights right. This last pass is
avoided when possible which is why some code below looks a bit more
complex than needed. The reason for such optimizations is that each cells
is actually a framed instance and because tables like this can be hundreds
of pages we want to keep processing time reasonable.

To a large extend the behaviour is comparable with the way bTABLE/eTABLE
works and there is a module that maps that one onto this one. Eventually
this mechamism will be improved so that it can replace its older cousin.

]]--

-- todo: use linked list instead of r/c array

local commands, context, tex, node = commands, context, tex, node

local texdimen    = tex.dimen
local texcount    = tex.count
local texbox      = tex.box
local texsetcount = tex.setcount
local texsetdimen = tex.setdimen

local format      = string.format
local concat      = table.concat
local points      = number.points

local context                 = context
local context_beginvbox       = context.beginvbox
local context_endvbox         = context.endvbox
local context_blank           = context.blank
local context_nointerlineskip = context.nointerlineskip

local variables               = interfaces.variables

local setmetatableindex       = table.setmetatableindex
local settings_to_hash        = utilities.parsers.settings_to_hash

local copy_node_list          = node.copy_list
local hpack_node_list         = node.hpack
local vpack_node_list         = node.vpack
local slide_node_list         = node.slide
local flush_node_list         = node.flush_list

local nodepool                = nodes.pool

local new_glue                = nodepool.glue
local new_kern                = nodepool.kern
local new_penalty             = nodepool.penalty
local new_hlist               = nodepool.hlist

local v_stretch               = variables.stretch
local v_normal                = variables.normal
local v_width                 = variables.width
local v_height                = variables.height
local v_repeat                = variables["repeat"]
local v_max                   = variables.max
local v_fixed                 = variables.fixed

local xtables                 = { }
typesetters.xtables           = xtables

local trace_xtable            = false
local report_xtable           = logs.reporter("xtable")

trackers.register("xtable.construct", function(v) trace_xtable = v end)

local null_mode = 0
local head_mode = 1
local foot_mode = 2
local more_mode = 3
local body_mode = 4

local namedmodes = { [0] =
    "null",
    "head",
    "foot",
    "next",
    "body",
}

local stack, data = { }, nil

function xtables.create(settings)
    table.insert(stack,data)
    local rows          = { }
    local widths        = { }
    local heights       = { }
    local depths        = { }
    local spans         = { }
    local distances     = { }
    local autowidths    = { }
    local modes         = { }
    local fixedrows     = { }
    local fixedcolumns  = { }
    local frozencolumns = { }
    local options       = { }
    data = {
        rows          = rows,
        widths        = widths,
        heights       = heights,
        depths        = depths,
        spans         = spans,
        distances     = distances,
        modes         = modes,
        autowidths    = autowidths,
        fixedrows     = fixedrows,
        fixedcolumns  = fixedcolumns,
        frozencolumns = frozencolumns,
        options       = options,
        nofrows       = 0,
        nofcolumns    = 0,
        currentrow    = 0,
        currentcolumn = 0,
        settings      = settings or { },
    }
    local function add_zero(t,k)
        t[k] = 0
        return 0
    end
    local function add_table(t,k)
        local v = { }
        t[k] = v
        return v
    end
    local function add_cell(row,c)
        local cell = {
            nx     = 0,
            ny     = 0,
            list   = false,
        }
        row[c] = cell
        if c > data.nofcolumns then
            data.nofcolumns = c
        end
        return cell
    end
    local function add_row(rows,r)
        local row = { }
        setmetatableindex(row,add_cell)
        rows[r] = row
        if r > data.nofrows then
            data.nofrows = r
        end
        return row
    end
    setmetatableindex(rows,add_row)
    setmetatableindex(widths,add_zero)
    setmetatableindex(heights,add_zero)
    setmetatableindex(depths,add_zero)
    setmetatableindex(distances,add_zero)
    setmetatableindex(modes,add_zero)
    setmetatableindex(fixedrows,add_zero)
    setmetatableindex(fixedcolumns,add_zero)
    setmetatableindex(options,add_table)
    --
    settings.columndistance = tonumber(settings.columndistance) or 0
    settings.rowdistance = tonumber(settings.rowdistance) or 0
    settings.leftmargindistance = tonumber(settings.leftmargindistance) or 0
    settings.rightmargindistance = tonumber(settings.rightmargindistance) or 0
    settings.options = settings_to_hash(settings.option)
    settings.textwidth = tonumber(settings.textwidth) or tex.hsize
    settings.lineheight = tonumber(settings.lineheight) or texdimen.lineheight
    settings.maxwidth = tonumber(settings.maxwidth) or settings.textwidth/8
 -- if #stack > 0 then
 --     settings.textwidth = tex.hsize
 -- end
    data.criterium_v =   2 * data.settings.lineheight
    data.criterium_h = .75 * data.settings.textwidth

end

function xtables.initialize_reflow_width(option)
    local r = data.currentrow
    local c = data.currentcolumn + 1
    local drc = data.rows[r][c]
    drc.nx = texcount.c_tabl_x_nx
    drc.ny = texcount.c_tabl_x_ny
    local distances = data.distances
    local distance = texdimen.d_tabl_x_distance
    if distance > distances[c] then
        distances[c] = distance
    end
    if option and option ~= "" then
        local options = settings_to_hash(option)
        data.options[r][c] = options
        if options[v_fixed] then
            data.frozencolumns[c] = true
        end
    end
    data.currentcolumn = c
end

-- local function rather_fixed(n)
--     for n in node.

function xtables.set_reflow_width()
    local r = data.currentrow
    local c = data.currentcolumn
    local rows = data.rows
    local row = rows[r]
    while row[c].span do -- can also be previous row ones
        c = c + 1
    end
    local tb = texbox.b_tabl_x
    local drc = row[c]
    --
    drc.list = true -- we don't need to keep the content around as we're in trial mode (no: copy_node_list(tb))
    --
    local widths, width = data.widths, tb.width
    if width > widths[c] then
        widths[c] = width
    end
    local heights, height = data.heights, tb.height
    if height > heights[r] then
        heights[r] = height
    end
    local depths, depth = data.depths, tb.depth
    if depth > depths[r] then
        depths[r] = depth
    end
    --
    local dimensionstate = texcount.frameddimensionstate
    local fixedcolumns = data.fixedcolumns
    local fixedrows = data.fixedrows
    if dimensionstate == 1 then
        if width > fixedcolumns[c] then -- how about a span here?
            fixedcolumns[c] = width
        end
    elseif dimensionstate == 2 then
        fixedrows[r]    = height
    elseif dimensionstate == 3 then
        fixedrows[r]    = height -- width
        fixedcolumns[c] = width -- height
    else -- probably something frozen, like an image -- we could parse the list
        if width <= data.criterium_h and height >= data.criterium_v then
            if width > fixedcolumns[c] then -- how about a span here?
                fixedcolumns[c] = width
            end
        end
    end
    drc.dimensionstate = dimensionstate
    --
    local nx, ny = drc.nx, drc.ny
    if nx > 1 or ny > 1 then
        local spans = data.spans
        local self = true
        for y=1,ny do
            for x=1,nx do
                if self then
                    self = false
                else
                    local ry = r + y - 1
                    local cx = c + x - 1
                    if y > 1 then
                        spans[ry] = true
                    end
                    rows[ry][cx].span = true
                end
            end
        end
        c = c + nx - 1
    end
    if c > data.nofcolumns then
        data.nofcolumns = c
    end
    data.currentcolumn = c
end

function xtables.initialize_reflow_height()
    local r = data.currentrow
    local c = data.currentcolumn + 1
    local rows = data.rows
    local row = rows[r]
    while row[c].span do -- can also be previous row ones
        c = c + 1
    end
    data.currentcolumn = c
    local widths = data.widths
    local w = widths[c]
    local drc = row[c]
    for x=1,drc.nx-1 do
        w = w + widths[c+x]
    end
    texdimen.d_tabl_x_width = w
    local dimensionstate = drc.dimensionstate or 0
    if dimensionstate == 1 or dimensionstate == 3 then
        -- width was fixed so height is known
        texcount.c_tabl_x_skip_mode = 1
    elseif dimensionstate == 2 then
        -- height is enforced
        texcount.c_tabl_x_skip_mode = 1
    elseif data.autowidths[c] then
        -- width has changed so we need to recalculate the height
        texcount.c_tabl_x_skip_mode = 0
    else
        texcount.c_tabl_x_skip_mode = 1
    end
end

function xtables.set_reflow_height()
    local r = data.currentrow
    local c = data.currentcolumn
    local rows = data.rows
    local row = rows[r]
--     while row[c].span do -- we could adapt drc.nx instead
--         c = c + 1
--     end
    local tb = texbox.b_tabl_x
    local drc = row[c]
    if data.fixedrows[r] == 0 then --  and drc.dimensionstate < 2
        local heights, height = data.heights, tb.height
        if height > heights[r] then
            heights[r] = height
        end
        local depths, depth = data.depths, tb.depth
        if depth > depths[r] then
            depths[r] = depth
        end
    end
--     c = c + drc.nx - 1
--     data.currentcolumn = c
end

function xtables.initialize_construct()
    local r = data.currentrow
    local c = data.currentcolumn + 1
    local rows = data.rows
    local row = rows[r]
    while row[c].span do -- can also be previous row ones
        c = c + 1
    end
    data.currentcolumn = c
    local widths = data.widths
    local heights = data.heights
    local depths = data.depths
    local w = widths[c]
    local h = heights[r]
    local d = depths[r]
    local drc = row[c]
    for x=1,drc.nx-1 do
        w = w + widths[c+x]
    end
    for y=1,drc.ny-1 do
        h = h + heights[r+y]
        d = d + depths[r+y]
    end
    texdimen.d_tabl_x_width = w
    texdimen.d_tabl_x_height = h + d
    texdimen.d_tabl_x_depth = 0
end

function xtables.set_construct()
    local r = data.currentrow
    local c = data.currentcolumn
    local rows = data.rows
    local row = rows[r]
--     while row[c].span do -- can also be previous row ones
--         c = c + 1
--     end
    local drc = row[c]
    -- this will change as soon as in luatex we can reset a box list without freeing
    drc.list = copy_node_list(texbox.b_tabl_x)
--     c = c + drc.nx - 1
--     data.currentcolumn = c
end

local function showwidths(where,widths,autowidths)
    local result = { }
    for i=1,#widths do
        result[#result+1] = format("%12s%s",points(widths[i]),autowidths[i] and "*" or " ")
    end
    return report_xtable("%s : %s",where,concat(result," "))
end

function xtables.reflow_width()
    local nofrows = data.nofrows
    local nofcolumns = data.nofcolumns
    local rows = data.rows
    for r=1,nofrows do
        local row = rows[r]
        for c=1,nofcolumns do
            local drc = row[c]
            if drc.list then
             -- flush_node_list(drc.list)
                drc.list = false
            end
        end
    end
    -- spread
    local settings = data.settings
    local options = settings.options
    local maxwidth = settings.maxwidth
    -- calculate width
    local widths = data.widths
    local distances = data.distances
    local autowidths = data.autowidths
    local fixedcolumns = data.fixedcolumns
    local frozencolumns = data.frozencolumns
    local width = 0
    local distance = 0
    local nofwide = 0
    local widetotal = 0
    local available = settings.textwidth - settings.leftmargindistance - settings.rightmargindistance
    if trace_xtable then
        showwidths("stage 1",widths,autowidths)
    end
    local noffrozen = 0
    if options[v_max] then
        for c=1,nofcolumns do
            width = width + widths[c]
            if width > maxwidth then
                autowidths[c] = true
                nofwide = nofwide + 1
                widetotal = widetotal + widths[c]
            end
            if c < nofcolumns then
                distance = distance + distances[c]
            end
            if frozencolumns[c] then
                noffrozen = noffrozen + 1 -- brr, should be nx or so
            end
        end
    else
        for c=1,nofcolumns do -- also keep track of forced
            local fixedwidth = fixedcolumns[c]
            if fixedwidth > 0 then
                widths[c] = fixedwidth
                width = width + fixedwidth
            else
                width = width + widths[c]
                if width > maxwidth then
                    autowidths[c] = true
                    nofwide = nofwide + 1
                    widetotal = widetotal + widths[c]
                end
            end
            if c < nofcolumns then
                distance = distance + distances[c]
            end
            if frozencolumns[c] then
                noffrozen = noffrozen + 1 -- brr, should be nx or so
            end
        end
    end
    if trace_xtable then
        showwidths("stage 2",widths,autowidths)
    end
    local delta = available - width - distance - (nofcolumns-1) * settings.columndistance
    if delta == 0 then
        -- nothing to be done
        if trace_xtable then
            report_xtable("perfect fit")
        end
    elseif delta > 0 then
        -- we can distribute some
        if not options[v_stretch] then
            -- not needed
            if trace_xtable then
                report_xtable("too wide but no stretch, delta %p",delta)
            end
        elseif options[v_width] then
            local factor = delta / width
            if trace_xtable then
                report_xtable("proportional stretch, delta %p, width %p, factor %a",delta,width,factor)
            end
            for c=1,nofcolumns do
                widths[c] = widths[c] + factor * widths[c]
            end
        else
            -- frozen -> a column with option=fixed will not stretch
            local extra = delta / (nofcolumns - noffrozen)
            if trace_xtable then
                report_xtable("normal stretch, delta %p, extra %p",delta,extra)
            end
            for c=1,nofcolumns do
                if not frozencolumns[c] then
                    widths[c] = widths[c] + extra
                end
            end
        end
    elseif nofwide > 0 then
        while true do
            done = false
            local available = (widetotal + delta) / nofwide
            if trace_xtable then
                report_xtable("shrink check, total %p, delta %p, columns %s, fixed %p",widetotal,delta,nofwide,available)
            end
            for c=1,nofcolumns do
                if autowidths[c] and available >= widths[c] then
                    autowidths[c] = nil
                    nofwide = nofwide - 1
                    widetotal = widetotal - widths[c]
                    done = true
                end
            end
            if not done then
                break
            end
        end
        -- maybe also options[v_width] here but tricky as width does not say
        -- much about amount
        if options[v_width] then -- not that much (we could have a clever vpack loop balancing .. no fun)
            local factor = (widetotal + delta) / width
            if trace_xtable then
                report_xtable("proportional shrink used, total %p, delta %p, columns %s, factor %s",widetotal,delta,nofwide,factor)
            end
            for c=1,nofcolumns do
                if autowidths[c] then
                    widths[c] = factor * widths[c]
                end
            end
        else
            local available = (widetotal + delta) / nofwide
            if trace_xtable then
                report_xtable("normal shrink used, total %p, delta %p, columns %s, fixed %p",widetotal,delta,nofwide,available)
            end
            for c=1,nofcolumns do
                if autowidths[c] then
                    widths[c] = available
                end
            end
        end
    end
    if trace_xtable then
        showwidths("stage 3",widths,autowidths)
    end
    --
    data.currentrow = 0
    data.currentcolumn = 0
end

function xtables.reflow_height()
    data.currentrow = 0
    data.currentcolumn = 0
    local settings = data.settings
    if settings.options[v_height] then
        local heights = data.heights
        local depths = data.depths
        local nofrows = data.nofrows
        local totalheight = 0
        local totaldepth = 0
        for i=1,nofrows do
            totalheight = totalheight + heights[i]
            totalheight = totalheight + depths [i]
        end
        local total = totalheight + totaldepth
        local leftover = settings.textheight - total
        if leftover > 0 then
            local leftheight = (totalheight / total ) * leftover / #heights
            local leftdepth  = (totaldepth  / total ) * leftover / #depths
            for i=1,nofrows do
                heights[i] = heights[i] + leftheight
                depths [i] = depths [i] + leftdepth
            end
        end
    end
end

local function showspans(data)
    local rows = data.rows
    local modes = data.modes
    local nofcolumns = data.nofcolumns
    local nofrows = data.nofrows
    for r=1,nofrows do
        local line = { }
        local row = rows[r]
        for c=1,nofcolumns do
            local cell =row[c]
            if cell.list then
                line[#line+1] = "list"
            elseif cell.span then
                line[#line+1] = "span"
            else
                line[#line+1] = "none"
            end
        end
        report_xtable("%3d : %s : % t",r,namedmodes[modes[r]] or "----",line)
    end
end

function xtables.construct()
    local rows = data.rows
    local heights = data.heights
    local depths = data.depths
    local widths = data.widths
    local spans = data.spans
    local distances = data.distances
    local modes = data.modes
    local settings = data.settings
    local nofcolumns = data.nofcolumns
    local nofrows = data.nofrows
    local columndistance = settings.columndistance
    local rowdistance = settings.rowdistance
    local leftmargindistance = settings.leftmargindistance
    local rightmargindistance = settings.rightmargindistance
    -- ranges can be mixes so we collect

    if trace_xtable then
        showspans(data)
    end

    local ranges = {
        [head_mode] = { },
        [foot_mode] = { },
        [more_mode] = { },
        [body_mode] = { },
    }
    for r=1,nofrows do
        local m = modes[r]
        if m == 0 then
            m = body_mode
        end
        local range = ranges[m]
        range[#range+1] = r
    end
    -- todo: hook in the splitter ... the splitter can ask for a chunk of
    -- a certain size ... no longer a split memory issue then and header
    -- footer then has to happen here too .. target height
    local function packaged_column(r)
        local row = rows[r]
        local start = nil
        local stop = nil
        if leftmargindistance > 0 then
            start = new_kern(leftmargindistance)
            stop = start
        end
        local hasspan = false
        for c=1,nofcolumns do
            local drc = row[c]
            if not hasspan then
                hasspan = drc.span
            end
            local list = drc.list
            if list then
                list.shift = list.height + list.depth
             -- list = hpack_node_list(list) -- is somehow needed
             -- list.width = 0
             -- list.height = 0
             -- list.depth = 0
                -- faster:
                local h = new_hlist()
                h.list = list
                list = h
                --
                if start then
                    stop.next = list
                    list.prev = stop
                else
                    start = list
                end
                stop = list -- one node anyway, so not needed: slide_node_list(list)
            end
            local step = widths[c]
            if c < nofcolumns then
                step = step + columndistance + distances[c]
            end
            local kern = new_kern(step)
            if stop then
                stop.prev = kern
                stop.next = kern
            else -- can be first spanning next row (ny=...)
                start = kern
            end
            stop = kern
        end
        if start then
            if rightmargindistance > 0 then
                local kern = new_kern(rightmargindistance)
                stop.next = kern
                kern.prev = stop
             -- stop = kern
            end
            return start, heights[r] + depths[r], hasspan
        end
    end
    local function collect_range(range)
        local result, nofr = { }, 0
        local nofrange = #range
        for i=1,#range do
            local r = range[i]
         -- local row = rows[r]
            local list, size, hasspan = packaged_column(r)
            if list then
                if hasspan and nofr > 0 then
                    result[nofr][4] = true
                end
                nofr = nofr + 1
                result[nofr] = {
                    hpack_node_list(list),
                    size,
                    i < nofrange and rowdistance > 0 and rowdistance or false, -- might move
                    false
                }
            end
        end
        return result
    end
    local body = collect_range(ranges[body_mode])
    data.results = {
        [head_mode] = collect_range(ranges[head_mode]),
        [foot_mode] = collect_range(ranges[foot_mode]),
        [more_mode] = collect_range(ranges[more_mode]),
        [body_mode] = body,
    }
    if #body == 0 then
        texsetcount("global","c_tabl_x_state",0)
        texsetdimen("global","d_tabl_x_final_width",0)
    else
        texsetcount("global","c_tabl_x_state",1)
        texsetdimen("global","d_tabl_x_final_width",body[1][1].width)
    end
end

local function inject(row,copy,package)
    local list = row[1]
    if copy then
        row[1] = copy_node_list(list)
    end
    if package then
        context_beginvbox()
        context(list)
        context(new_kern(row[2]))
        context_endvbox()
        context_nointerlineskip() -- figure out a better way
        if row[4] then
            -- nothing as we have a span
        elseif row[3] then
            context_blank(row[3] .. "sp") -- why blank ?
        else
            context(new_glue(0))
        end
    else
        context(list)
        context(new_kern(row[2]))
        if row[3] then
            context(new_glue(row[3]))
        end
    end
end

local function total(row,distance)
    local n = #row > 0 and rowdistance or 0
    for i=1,#row do
        local ri = row[i]
        n = n + ri[2] + (ri[3] or 0)
    end
    return n
end

-- local function append(list,what)
--     for i=1,#what do
--         local l = what[i]
--         list[#list+1] = l[1]
--         local k = l[2] + (l[3] or 0)
--         if k ~= 0 then
--             list[#list+1] = new_kern(k)
--         end
--     end
-- end

local function spanheight(body,i)
    local height, n = 0, 1
    while true do
        local bi = body[i]
        if bi then
            height = height + bi[2] + (bi[3] or 0)
            if bi[4] then
                n = n + 1
                i = i + 1
            else
                break
            end
        else
            break
        end
    end
    return height, n
end

function xtables.flush(directives) -- todo split by size / no inbetween then ..  glue list kern blank
    local vsize = directives.vsize
    local method = directives.method or v_normal
    local settings = data.settings
    local results  = data.results
    local rowdistance = settings.rowdistance
    local head = results[head_mode]
    local foot = results[foot_mode]
    local more = results[more_mode]
    local body = results[body_mode]
    local repeatheader = settings.header == v_repeat
    local repeatfooter = settings.footer == v_repeat
    if vsize and vsize > 0 then
        context_beginvbox()
        local bodystart = data.bodystart or 1
        local bodystop  = data.bodystop or #body
        if bodystart > 0 and bodystart <= bodystop then
            local bodysize = vsize
            local footsize = total(foot,rowdistance)
            local headsize = total(head,rowdistance)
            local moresize = total(more,rowdistance)
            local firstsize, firstspans = spanheight(body,bodystart)
            if bodystart == 1 then -- first chunk gets head
                bodysize = bodysize - headsize - footsize
                if headsize > 0 and bodysize >= firstsize then
                    for i=1,#head do
                        inject(head[i],repeatheader)
                    end
                    if rowdistance > 0 then
                        context(new_glue(rowdistance))
                    end
                    if not repeatheader then
                        results[head_mode] = { }
                    end
                end
            elseif moresize > 0 then -- following chunk gets next
                bodysize = bodysize - footsize - moresize
                if bodysize >= firstsize then
                    for i=1,#more do
                        inject(more[i],true)
                    end
                    if rowdistance > 0 then
                        context(new_glue(rowdistance))
                    end
                end
            elseif headsize > 0 and repeatheader then -- following chunk gets head
                bodysize = bodysize - footsize - headsize
                if bodysize >= firstsize then
                    for i=1,#head do
                        inject(head[i],true)
                    end
                    if rowdistance > 0 then
                        context(new_glue(rowdistance))
                    end
                end
            else -- following chunk gets nothing
                bodysize = bodysize - footsize
            end
            if bodysize >= firstsize then
                local i = bodystart
                while i <= bodystop do -- room for improvement
                    local total, spans = spanheight(body,i)
                    local bs = bodysize - total
                    if bs > 0 then
                        bodysize = bs
                        for s=1,spans do
                            inject(body[i])
                            body[i] = nil
                            i = i + 1
                        end
                        bodystart = i
                    else
                        break
                    end
                end
                if bodystart > bodystop then
                    -- all is flushed and footer fits
                    if footsize > 0 then
                        if rowdistance > 0 then
                            context(new_glue(rowdistance))
                        end
                        for i=1,#foot do
                            inject(foot[i])
                        end
                        results[foot_mode] = { }
                    end
                    results[body_mode] = { }
                    texsetcount("global","c_tabl_x_state",0)
                else
                    -- some is left so footer is delayed
                    -- todo: try to flush a few more lines
                    if repeatfooter and footsize > 0 then
                        if rowdistance > 0 then
                            context(new_glue(rowdistance))
                        end
                        for i=1,#foot do
                            inject(foot[i],true)
                        end
                    else
                        -- todo: try to fit more of body
                    end
                    texsetcount("global","c_tabl_x_state",2)
                end
            else
                if firstsize > vsize then
                    -- get rid of the too large cell
                    for s=1,firstspans do
                        inject(body[bodystart])
                        body[bodystart] = nil
                        bodystart = bodystart + 1
                    end
                end
                texsetcount("global","c_tabl_x_state",2) -- 1
            end
        else
            texsetcount("global","c_tabl_x_state",0)
        end
        data.bodystart = bodystart
        data.bodystop = bodystop
        context_endvbox()
    else
        if method == variables.split then
            -- maybe also a non float mode with header/footer repeat although
            -- we can also use a float without caption
            for i=1,#head do
                inject(head[i],false,true)
            end
            if #head > 0 and rowdistance > 0 then
                context_blank(rowdistance .. "sp")
            end
            for i=1,#body do
                inject(body[i],false,true)
            end
            if #foot > 0 and rowdistance > 0 then
                context_blank(rowdistance .. "sp")
            end
            for i=1,#foot do
                inject(foot[i],false,true)
            end
        else -- normal
            context_beginvbox()
            for i=1,#head do
                inject(head[i])
            end
            if #head > 0 and rowdistance > 0 then
                context(new_glue(rowdistance))
            end
            for i=1,#body do
                inject(body[i])
            end
            if #foot > 0 and rowdistance > 0 then
                context(new_glue(rowdistance))
            end
            for i=1,#foot do
                inject(foot[i])
            end
            context_endvbox()
        end
        results[head_mode] = { }
        results[body_mode] = { }
        results[foot_mode] = { }
        texsetcount("global","c_tabl_x_state",0)
    end
end

function xtables.cleanup()
    for mode, result in next, data.results do
        for _, r in next, result do
            flush_node_list(r[1])
        end
    end
    data = table.remove(stack)
end

function xtables.next_row()
    local r = data.currentrow + 1
    data.modes[r] = texcount.c_tabl_x_mode
    data.currentrow = r
    data.currentcolumn = 0
end

-- eventually we might only have commands

commands.x_table_create             = xtables.create
commands.x_table_reflow_width       = xtables.reflow_width
commands.x_table_reflow_height      = xtables.reflow_height
commands.x_table_construct          = xtables.construct
commands.x_table_flush              = xtables.flush
commands.x_table_cleanup            = xtables.cleanup
commands.x_table_next_row           = xtables.next_row
commands.x_table_init_reflow_width  = xtables.initialize_reflow_width
commands.x_table_init_reflow_height = xtables.initialize_reflow_height
commands.x_table_init_construct     = xtables.initialize_construct
commands.x_table_set_reflow_width   = xtables.set_reflow_width
commands.x_table_set_reflow_height  = xtables.set_reflow_height
commands.x_table_set_construct      = xtables.set_construct
