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

local type, unpack, rawset = type, unpack, type
local loadstring = loadstring
local sind, cosd, abs = math.sind, math.cosd, math.abs
local insert, remove = table.insert, table.remove

local context             = context
local implement           = interfaces.implement

local allocate            = utilities.storage.allocate

local formatters          = string.formatters

local get                 = tokens.accessors.index

local scanners            = tokens.scanners
local scaninteger         = scanners.integer
local scanstring          = scanners.string
local scankeyword         = scanners.keyword
local scantokenlist       = scanners.tokenlist
local scannumber          = scanners.number

local nuts                = nodes.nuts
local tonode              = nuts.tonode
local copynut             = nuts.copy
local nutpool             = nuts.pool
local nodepool            = nodes.pool

local nodeproperties      = nodes.properties.data

local register            = nutpool.register
local newnut              = nuts.new
local tonode              = nodes.tonode

-- Whatsits are a generic node type with very few fields. We use them to implement
-- whatever we like. Some are good old tex whatsits but many are specific to the
-- macro package. They are part of the backend.

backends                  = backends or { }
local backends            = backends

local whatsitcodes        = allocate { }
nodes.whatsitcodes        = whatsitcodes
local lastwhatsit         = 0

local whatsit_code        <const> = nodes.nodecodes.whatsit

nodes.subtypes.whatsit       = whatsitcodes
nodes.subtypes[whatsit_code] = whatsitcodes

local function registerwhatsit(name)
    lastwhatsit = lastwhatsit + 1
    whatsitcodes[lastwhatsit] = name
    whatsitcodes[name] = lastwhatsit
    return lastwhatsit
end

local function registerwhatsitnode(name)
    return register(newnut(whatsit_code,registerwhatsit(name)))
end

-- We only have a subset of literals. In fact, we try to avoid literals.

do

    local literalvalues = allocate { }
    nodes.literalvalues = literalvalues
    local lastliteral   = 0
    local literalnode   = registerwhatsitnode("literal")

    local function registerliteral(name,alias)
        lastliteral = lastliteral + 1
        literalvalues[lastliteral] = name
        literalvalues[name] = lastliteral
        if alias then
            literalvalues[alias] = lastliteral
        end
        return lastliteral
    end

    local originliteral_code = registerliteral("origin")
    local pageliteral_code   = registerliteral("page")
    local directliteral_code = registerliteral("always","direct")
    local rawliteral_code    = registerliteral("raw")
    local textliteral_code   = registerliteral("text") -- not to be used
    local fontliteral_code   = registerliteral("font") -- not to be used

    function nutpool.originliteral(str) local t = copynut(literalnode) nodeproperties[t] = { data = str, mode = originliteral_code } return t end
    function nutpool.pageliteral  (str) local t = copynut(literalnode) nodeproperties[t] = { data = str, mode = pageliteral_code   } return t end
    function nutpool.directliteral(str) local t = copynut(literalnode) nodeproperties[t] = { data = str, mode = directliteral_code } return t end
    function nutpool.rawliteral   (str) local t = copynut(literalnode) nodeproperties[t] = { data = str, mode = rawliteral_code    } return t end

    local pdfliterals = {
        [originliteral_code] = originliteral_code, [literalvalues[originliteral_code]] = originliteral_code,
        [pageliteral_code]   = pageliteral_code,   [literalvalues[pageliteral_code]]   = pageliteral_code,
        [directliteral_code] = directliteral_code, [literalvalues[directliteral_code]] = directliteral_code,
        [rawliteral_code]    = rawliteral_code,    [literalvalues[rawliteral_code]]    = rawliteral_code,
    }

    function nutpool.literal(mode,str)
        local t = copynut(literalnode)
        if str then
            nodeproperties[t] = { data = str, mode = pdfliterals[mode] or pageliteral_code }
        else
            nodeproperties[t] = { data = mode, mode = pageliteral_code }
        end
        return t
    end

end

-- The latelua node is just another whatsit and we handle the \LUA\ code with
-- other \LUA\ code, contrary to \LUATEX\ where it's a native node.

do

    local getdata     = nuts.getdata
    local serialize   = token.serialize

    local lateluanode = registerwhatsitnode("latelua")
    local noflatelua  = 0

    function nutpool.latelua(code)
        local n = copynut(lateluanode)
        nodeproperties[n] = { data = code }
        return n
    end

    function backends.latelua(current,pos_h,pos_v,pos_r) -- todo: pass pos_h and pos_v (more efficient in lmtx)
        local prop = nodeproperties[current]
        local data = prop and prop.data or getdata(current)
        local kind = type(data)
        noflatelua = noflatelua + 1
        if kind == "table" then
            data.action(data.specification or data,current,pos_h,pos_v,pos_r)
        elseif kind == "function" then
            data(current,pos_h,pos_v,pos_r)
        else
            if kind ~= "string" then
                data = serialize(data)
            end
            if data and #data ~= "" then
                local code = loadstring(data)
                if code then
                    code(current,pos_h,pos_v,pos_r)
                end
            end
        end
    end

    function backends.getcallbackstate()
        return { count = noflatelua }
    end

    implement {
        name      = "latelua",
        public    = true,
        protected = true,
        untraced  = true,
        actions   = function()
            local node = copynut(lateluanode)
            local name = "latelua"
            if scankeyword("name") then
                name = scanstring()
            end
            local data = scantokenlist()
            nodeproperties[node] = { name = name, data = data }
            return context(tonode(node))
        end,
    }

end

-- This can be anything. Dealing with these nodes is done via properties, as with
-- the other whatsits.

do

    local usernode = registerwhatsitnode("userdefined")

    local userids  = allocate()
    local lastid   = 0

    setmetatable(userids, {
        __index = function(t,k)
            if type(k) == "string" then
                lastid = lastid + 1
                rawset(userids,lastid,k)
                rawset(userids,k,lastid)
                return lastid
            else
                rawset(userids,k,k)
                return k
            end
        end,
        __call = function(t,k)
            return t[k]
        end
    } )

    function nutpool.userdefined(id,data)
        local n = copynut(usernode)
        nodeproperties[n] = { id = id, data = data }
        return n
    end

    nutpool .usernode = nutpool.userdefined
    nutpool .userids  = userids
    nodepool.userids  = userids

end

-- This one is only used by generic packages.

do

    local saveposnode = registerwhatsitnode("savepos")

    function nutpool.savepos()
        return copynut(saveposnode)
    end

end

do

    local savenode      = registerwhatsitnode("save")
    local restorenode   = registerwhatsitnode("restore")
    local setmatrixnode = registerwhatsitnode("setmatrix")

    local stack         = { }
    local restore       = true -- false

    -- these are old school nodes

    function nutpool.save()
        return copynut(savenode)
    end

    function nutpool.restore()
        return copynut(restorenode)
    end

    function nutpool.setmatrix(rx,sx,sy,ry,tx,ty)
        local t = copynut(setmatrixnode)
        nodeproperties[t] = { matrix = { rx, sx, sy, ry, tx, ty } }
        return t
    end

    implement {
        name    = "startgraphicgroup",
        actions = function()
            context(tonode(copynut(savenode)))
        end,
    }

    implement {
        name    = "stopgraphicgroup",
        actions = function()
            context(tonode(copynut(restorenode)))
        end,
    }

    -- common

    local function stopcommon(n)
        local top = remove(stack)
        if top == false then
            return -- not wrapped
        elseif top == true then
            return copynut(n)
        elseif top then
            local t = copynut(n)
            nodeproperties[t] = { matrix = { unpack(top) } }
            return t
        else
            return -- nesting error
        end
    end

    -- matrix

    local startmatrixnode = registerwhatsitnode("startmatrix")
    local stopmatrixnode  = registerwhatsitnode("stopmatrix")

    local function startmatrix(rx, sx, sy, ry)
        if rx == 1 and sx == 0 and sy == 0 and ry == 1 then
            insert(stack,false)
        else
            local t = copynut(startmatrixnode)
            nodeproperties[t] = { matrix = { rx, sx, sy, ry } }
            insert(stack,store and { -rx, -sx, -sy, -ry } or true)
            return t
        end
    end

    local function stopmatrix()
        return stopcommon(stopmatrixnode)
    end

    implement {
        name    = "startmatrix",
        actions = function()
            local rx, sx, sy, ry = 1, 0, 0, 1
            while true do
                    if scankeyword("rx") then rx = scannumber()
                elseif scankeyword("ry") then ry = scannumber()
                elseif scankeyword("sx") then sx = scannumber()
                elseif scankeyword("sy") then sy = scannumber()
                else   break end
            end
            local t = startmatrix(rx,sx,sy,ry)
            if t then
                context(tonode(t))
            end
        end,
    }

    implement {
        name    = "stopmatrix",
        actions = function()
            local t = stopmatrix(n)
            if t then
                context(tonode(t))
            end
        end,
    }

    -- scale

    local startscalingnode = registerwhatsitnode("startscaling")
    local stopscalingnode  = registerwhatsitnode("stopscaling")

    local function startscaling(rx,ry) -- at the tex end we use sx and sy instead of rx and ry
        if rx == 1 and ry == 1 then
            insert(stack,false)
        else
            if rx == 0 then
                rx = 0.0001
            end
            if ry == 0 then
                ry = 0.0001
            end
            local t = copynut(startscalingnode)
            nodeproperties[t] = { matrix = { rx, 0, 0, ry } }
            insert(stack,restore and { 1/rx, 0, 0, 1/ry } or true)
            return t
        end
    end

    local function stopscaling() -- at the tex end we use sx and sy instead of rx and ry
        return stopcommon(stopscalingnode)
    end

    nutpool.startscaling = startscaling
    nutpool.stopscaling  = stopscaling

    implement {
        name    = "startscaling",
        actions = function()
            local rx, ry = 1, 1
            while true do
                if scankeyword("rx") then
                    rx = scannumber()
                elseif scankeyword("ry") then
                    ry = scannumber()
                else
                    break
                end
            end
            local t = startscaling(rx,ry)
            if t then
                context(tonode(t))
            end
        end,
    }

    implement {
        name    = "stopscaling",
        actions = function()
            local t = stopscaling(n)
            if t then
                context(tonode(t))
            end
        end,
    }

    -- rotate

    local startrotationnode = registerwhatsitnode("startrotation")
    local stoprotationnode  = registerwhatsitnode("stoprotation")

    local function startrotation(a)
        if a == 0 then
            insert(stack,false)
        else
            local s, c = sind(a), cosd(a)
            if abs(s) < 0.000001 then
                s = 0 -- otherwise funny -0.00000
            end
            if abs(c) < 0.000001 then
                c = 0 -- otherwise funny -0.00000
            end
            local t = copynut(startrotationnode)
            nodeproperties[t] = { matrix = { c, s, -s, c } }
            insert(stack,restore and { c, -s, s, c } or true)
            return t
        end
    end

    local function stoprotation()
        return stopcommon(stoprotationnode)
    end

    nutpool.startrotation = startrotation
    nutpool.stoprotation  = stoprotation

    implement {
        name    = "startrotation",
        actions = function()
            local n = scannumber()
            local t = startrotation(n)
            if t then
                context(tonode(t))
            end
        end,
    }

    implement {
        name    = "stoprotation",
        actions = function()
            local t = stoprotation()
            if t then
                context(tonode(t))
            end
        end,
    }

    -- mirror

    local startmirroringnode = registerwhatsitnode("startmirroring")
    local stopmirroringnode  = registerwhatsitnode("stopmirroring")

    local function startmirroring()
        local t = copynut(startmirroringnode)
        nodeproperties[t] = { matrix = { -1, 0, 0, 1 } }
        return t
    end

    local function stopmirroring()
        local t = copynut(stopmirroringnode)
        nodeproperties[t] = { matrix = { -1, 0, 0, 1 } }
        return t
    end

    implement {
        name    = "startmirroring",
        actions = function()
            context(tonode(startmirroring()))
        end,
    }

    implement {
        name    = "stopmirroring",
        actions = function()
            context(tonode(stopmirroring()))
        end,
    }

    -- clip

    local startclippingnode = registerwhatsitnode("startclipping")
    local stopclippingnode  = registerwhatsitnode("stopclipping")

    local function startclipping(path)
        local t = copynut(startclippingnode)
        nodeproperties[t] = { path = path }
        return t
    end

    local function stopclipping()
        return copynut(stopclippingnode)
    end

    implement {
        name    = "startclipping",
        actions = function()
            context(tonode(startclipping(scanstring())))
        end
    }

    implement {
        name    = "stopclipping",
        actions = function()
            context(tonode(stopclipping()))
        end,
    }

end

-- The (delayed and immediate) write operations are emulated in \LUA\ and presented
-- as primitives at the \TEX\ end.

do

    local logwriter = logs.writer
    local openfile  = io.open
    local flushio   = io.flush
    local serialize = token.serialize

    local opennode  = registerwhatsitnode("open")
    local writenode = registerwhatsitnode("write")
    local closenode = registerwhatsitnode("close")
    local channels  = { }

    local toconsole = logs.reporter("just a message")

    local immediate_code <const> = tex.flagcodes.immediate

    function backends.openout(n)
        local p = nodeproperties[n]
        if p then
            local handle = openfile(p.filename,"wb") or false
            if handle then
                channels[p.channel] = handle
            else
                -- error
            end
        end
    end


    function backends.writeout(n)
        local p = nodeproperties[n]
        if p then
            local handle  = channels[p.channel]
            local content = serialize(p.data)
            if handle then
                handle:write(content,"\n")
            else
                logwriter(content,"\n")
             -- toconsole(content)
            end
        end
    end

    function backends.closeout(n)
        local p = nodeproperties[n]
        if p then
            local channel = p.channel
            local handle  = channels[channel]
            if handle then
                handle:close()
                channels[channel] = false
                flushio()
            else
                -- error
            end
        end
    end

    local function immediately(prefix)
        return prefix and (prefix & immediate_code) ~= 0
    end

    implement {
        name    = "openout",
        public  = true,
        usage   = "value",
        actions = function(prefix)
            local channel = scaninteger()
            scankeyword("=") -- hack
            local filename = scanstring()
            if not immediately(prefix) then
                local n = copynut(opennode)
                nodeproperties[n] = { channel = channel, filename = filename } -- action = "open"
                return context(tonode(n))
            elseif not channels[channel] then
                local handle = openfile(filename,"wb") or false
                if handle then
                    channels[channel] = handle
                else
                    -- error
                end
            end
        end,
    }

    implement {
        name    = "write",
        public  = true,
        usage   = "value",
        actions = function(prefix)
            local channel = scaninteger()
            if not immediately(prefix) then
                local t = scantokenlist()
                local n = copynut(writenode)
                nodeproperties[n] = { channel = channel, data = t } -- action = "write"
                return context(tonode(n))
            else
                local content = scanstring()
                local handle  = channels[channel]
                if handle then
                    handle:write(content,"\n")
                else
                    toconsole(content)
--                    logwriter(content,"\n")
                end
            end
        end,
    }

    implement {
        name    = "closeout",
        public  = true,
        usage   = "value",
        actions = function(prefix)
            local channel = scaninteger()
            if not immediately(prefix) then
                local n = copynut(closenode)
                nodeproperties[n] = { channel = channel } -- action = "close"
                return context(tonode(n))
            else
                local handle = channels[channel]
                if handle then
                    handle:close()
                    channels[channel] = false
                    flushio()
                else
                    -- error
                end
            end
        end,
    }

    -- so we have them defined and can use them at the tex end .. still needed?

    local open_command  = get(token.create("openout"))
    local write_command = get(token.create("write"))
    local close_command = get(token.create("closeout"))

end

-- states: these get injected independent of grouping

do

    local setstatenode = registerwhatsitnode("setstate")

    function nutpool.setstate(data)
        local n = copynut(setstatenode)
        nodeproperties[n] = { data = data }
        return n
    end

end

-- We don't support specials so we make them dummies.

do
    implement {
        name      = "special",
        actions   = scanstring,
        public    = true,
        protected = true,
    }
end
