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

local type = type
local addsuffix = file.addsuffix

local setmetatableindex = table.setmetatableindex
local formatters        = string.formatters

local starttiming       = statistics.starttiming
local stoptiming        = statistics.stoptiming

local report            = logs.reporter("drivers")

local instances         = { }
local helpers           = { }
local converters        = { }
local prepared          = { }
local wrappedup         = { }
local cleanedup         = { }
local currentdriver     = "default"
local currentinstance   = nil

local shipout           = tex.shipout
local texgetbox         = tex.getbox
local texgetcount       = tex.getcount

local c_realpageno      <const> = tex.iscount("realpageno")

function converters.engine(driver,boxnumber,mode,number,specification)
    return shipout(boxnumber)
end

local prepare        = nil
local convert        = nil
local wrapup         = nil
local cleanup        = nil
local outputfilename = nil
local initialize     = nil
local finalize       = nil

drivers = drivers or {
    instances   = instances,
    helpers     = helpers,
    converters  = converters,
    lmtxversion = 0.10,
    report      = report,
}

local dummy = function() end

local defaulthandlers = {
    prepare         = dummy,
    initialize      = dummy,
    finalize        = dummy,
    updatefontstate = dummy,
    wrapup          = dummy,
    cleanup         = dummy,
    convert         = dummy,
    outputfilename  = dummy,
}

local installwhatsits  do

    local round       = math.round

    local leaderlevel = 0
    local backends    = backends
    local trace       = true

    local latelua     = backends.latelua
    local writeout    = backends.writeout
    local openout     = backends.openout
    local closeout    = backends.closeout

    trackers.register("backend.whatsits",function(v) trace = v end)

    local function pushleaderlevel()
        leaderlevel = leaderlevel + 1
    end

    local function popleaderlevel()
        leaderlevel = leaderlevel - 1
    end

    local function flushlatelua(current,h,v,r)
        -- Here we assume management by the lua function so currently we don't
        -- need to check for leaderlevel and it can even be counterproductive.
        return latelua(current,h,v,r)
    end

    local function flushwriteout(current)
        if leaderlevel == 0 then
            writeout(current)
        end
    end

    local function flushopenout(current)
        if leaderlevel == 0 then
            openout(current)
        end
    end

    local function flushcloseout(current)
        if leaderlevel == 0 then
            closeout(current)
        end
    end

    local function flushsavepos(current,pos_h,pos_v,pos_r)
        local jp = job.positions
        jp.lastx = round(pos_h)
        jp.lasty = round(pos_v)
        jp.lastr = pos_r or 0
    end

    local function flushuserdefined()
        -- nothing here
    end

    local whatsitcodes = nodes.whatsitcodes

    installwhatsits = function(name,flushers)

        -- latelua     : specific
        -- userdefined : special purpose, handled in callbacks
        -- savepos     : only used by generic packages
        -- open        : generic
        -- close       : generic
        -- write       : generic

        -- An alternative is to have a first time setter.

        local function checkagain(t, k)
            local v = rawget(flushers,k) -- in case it's registered later
            if not v then
                if trace then
                    report("unsupported whatsit %a in driver %a",whatsitcodes[k] or k,name)
                end
                v = function() end
            end
            t[k] = v
            return v
        end

        -- delayed

        local whatsit ; whatsit = setmetatableindex ( {
            [whatsitcodes.literal]        = flushers.literal        or function(...) return checkagain(whatsit,whatsitcodes.literal       )(...) end,
            [whatsitcodes.save]           = flushers.save           or function(...) return checkagain(whatsit,whatsitcodes.save          )(...) end,
            [whatsitcodes.restore]        = flushers.restore        or function(...) return checkagain(whatsit,whatsitcodes.restore       )(...) end,
            [whatsitcodes.setmatrix]      = flushers.setmatrix      or function(...) return checkagain(whatsit,whatsitcodes.setmatrix     )(...) end,
            [whatsitcodes.startmatrix]    = flushers.startmatrix    or function(...) return checkagain(whatsit,whatsitcodes.startmatrix   )(...) end,
            [whatsitcodes.stopmatrix]     = flushers.stopmatrix     or function(...) return checkagain(whatsit,whatsitcodes.stopmatrix    )(...) end,
            [whatsitcodes.startscaling]   = flushers.startscaling   or function(...) return checkagain(whatsit,whatsitcodes.startscaling  )(...) end,
            [whatsitcodes.stopscaling]    = flushers.stopscaling    or function(...) return checkagain(whatsit,whatsitcodes.stopscaling   )(...) end,
            [whatsitcodes.startrotation]  = flushers.startrotation  or function(...) return checkagain(whatsit,whatsitcodes.startrotation )(...) end,
            [whatsitcodes.stoprotation]   = flushers.stoprotation   or function(...) return checkagain(whatsit,whatsitcodes.stoprotation  )(...) end,
            [whatsitcodes.startmirroring] = flushers.startmirroring or function(...) return checkagain(whatsit,whatsitcodes.startmirroring)(...) end,
            [whatsitcodes.stopmirroring]  = flushers.stopmirroring  or function(...) return checkagain(whatsit,whatsitcodes.stopmirroring )(...) end,
            [whatsitcodes.startclipping]  = flushers.startclipping  or function(...) return checkagain(whatsit,whatsitcodes.startclipping )(...) end,
            [whatsitcodes.stopclipping]   = flushers.stopclipping   or function(...) return checkagain(whatsit,whatsitcodes.stopclippin   )(...) end,
            [whatsitcodes.setstate]       = flushers.setstate       or function(...) return checkagain(whatsit,whatsitcodes.setstate      )(...) end,
            --
            [whatsitcodes.latelua]        = flushlatelua,
            [whatsitcodes.userdefined]    = flushuserdefined,
            [whatsitcodes.savepos]        = flushsavepos,
            [whatsitcodes.open]           = flushopenout,
            [whatsitcodes.close]          = flushcloseout,
            [whatsitcodes.write]          = flushwriteout,
        }, checkagain)

        flushers.whatsit = whatsit

        flushers.pushleaderlevel = pushleaderlevel
        flushers.popleaderlevel  = popleaderlevel

    end

end

function drivers.install(specification)
    local name = specification.name
    if not name then
        report("missing driver name")
        return
    end
    local actions = specification.actions
    if not actions then
        report("no actions for driver %a",name)
        return
    end
    local flushers = specification.flushers
    if not flushers then
        report("no flushers for driver %a",name)
        return
    end
    installwhatsits(name,flushers)
 -- report("driver %a is installed",name)
    setmetatableindex(actions, defaulthandlers)
    setmetatableindex(flushers, function() return dummy end)
    instances[name] = specification
end

function drivers.initialize(...)
    starttiming(drivers)
    initialize(...)
    stoptiming(drivers)
end

function drivers.finalize(...)
    starttiming(drivers)
    finalize(...)
    stoptiming(drivers)
end

function drivers.convert(boxnumber)
    if currentinstance then
        callbacks.functions.start_page_number()
        starttiming(drivers)
        convert(currentinstance,boxnumber,texgetcount(c_realpageno))
        stoptiming(drivers)
        callbacks.functions.stop_page_number()
    end
end

function drivers.outputfilename()
    if currentinstance then
        return outputfilename(currentinstance)
    end
end

luatex.wrapup(function()
    if currentinstance and wrapup and not wrappedup[currentdriver] then
        starttiming(drivers)
        wrapup(currentinstance)
        stoptiming(drivers)
        wrappedup[currentdriver] = true
        cleanedup[currentdriver] = true
    end
end)

luatex.cleanup(function()
    if currentinstance and cleanup and not cleanedup[currentdriver] then
        starttiming(drivers)
        cleanup(currentinstance)
        stoptiming(drivers)
        wrappedup[currentdriver] = true
        cleanedup[currentdriver] = true
    end
end)

function drivers.enable(name)
    if name ~= currentdriver then
        if currentinstance then
            starttiming(drivers)
            cleanup(currentinstance)
            stoptiming(drivers)
        end
        currentdriver   = name or "none"
        currentinstance = instances[currentdriver]
        if currentinstance then
            local actions  = currentinstance.actions
            prepare        = actions.prepare
            wrapup         = actions.wrapup
            cleanup        = actions.cleanup
            convert        = actions.convert
            outputfilename = actions.outputfilename
            initialize     = actions.initialize
            finalize       = actions.finalize
            --
            if prepare and not prepared[currentdriver] then
                starttiming(drivers)
                prepare(currentinstance)
                stoptiming(drivers)
                prepared[currentdriver] = true
            end
        else
            report("bad driver")
        end
    end
end

statistics.register("driver time",function()
    return statistics.elapsedseconds(drivers)
end)

interfaces.implement {
    name      = "shipoutpage",
    arguments = "integer",
    actions   = drivers.convert,
}

interfaces.implement {
    name      = "enabledriver",
    arguments = "string",
    actions   = drivers.enable,
}

do

    local function prepare(driver)
        converter = drivers.converters.lmtx
    end

    local function convert(driver,boxnumber,pagenumber)
        converter(driver,texgetbox(boxnumber),"page",pagenumber)
    end

    drivers.install {
        name     = "empty",
        actions  = {
            prepare = prepare,
            convert = convert,
        },
        flushers = { },
    }

end

setmetatableindex(instances,function() return instances.default end)

-- We default to no driver at all:

drivers.install {
    name     = "none",
    actions  = { },
    flushers = { },
}

drivers.enable("none")
