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

-- Some analysis by Idris:
--
-- 1. Assuming the reading- vs word-order distinction (bidi-char types) is governing;
-- 2. Assuming that 'ARAB' represents an actual arabic string in raw input order, not word-order;
-- 3. Assuming that 'BARA' represent the correct RL word order;
--
-- Then we have, with input: LATIN ARAB
--
-- \textdir TLT LATIN ARAB => LATIN BARA
-- \textdir TRT LATIN ARAB => LATIN BARA
-- \textdir TRT LRO LATIN ARAB => LATIN ARAB
-- \textdir TLT LRO LATIN ARAB => LATIN ARAB
-- \textdir TLT RLO LATIN ARAB => NITAL ARAB
-- \textdir TRT RLO LATIN ARAB => NITAL ARAB

-- elseif d == "es"  then -- European Number Separator
-- elseif d == "et"  then -- European Number Terminator
-- elseif d == "cs"  then -- Common Number Separator
-- elseif d == "nsm" then -- Non-Spacing Mark
-- elseif d == "bn"  then -- Boundary Neutral
-- elseif d == "b"   then -- Paragraph Separator
-- elseif d == "s"   then -- Segment Separator
-- elseif d == "ws"  then -- Whitespace
-- elseif d == "on"  then -- Other Neutrals

-- todo  : delayed inserts here
-- todo  : get rid of local functions here
-- beware: math adds whatsits afterwards so that will mess things up
-- todo  : use new dir functions
-- todo  : make faster
-- todo  : move dir info into nodes
-- todo  : swappable tables and floats i.e. start-end overloads (probably loop in builders)
-- todo  : check if we still have crashes in luatex when non-matched (used to be the case)

-- I removed the original tracing code and now use the colorful one. If I ever want to change
-- something I will just inject prints for tracing.

local nodes, node = nodes, node

local trace_directions   = false  trackers.register("typesetters.directions.default", function(v) trace_directions = v end)

local report_directions  = logs.reporter("typesetting","text directions")

local nuts               = nodes.nuts
local tonut              = nuts.tonut
local tonode             = nuts.tonode
local nutstring          = nuts.tostring

local getnext            = nuts.getnext
local getprev            = nuts.getprev
local getfont            = nuts.getfont
local getchar            = nuts.getchar
local getid              = nuts.getid
local getsubtype         = nuts.getsubtype
local getlist            = nuts.getlist
local getfield           = nuts.getfield
local setfield           = nuts.setfield
local getattr            = nuts.getattr
local getprop            = nuts.getprop
local setprop            = nuts.setprop

local insert_node_before = nuts.insert_before
local insert_node_after  = nuts.insert_after
local remove_node        = nuts.remove
local end_of_math        = nuts.end_of_math

local nodepool           = nuts.pool

local nodecodes          = nodes.nodecodes
local whatcodes          = nodes.whatcodes
local mathcodes          = nodes.mathcodes

local glyph_code         = nodecodes.glyph
local whatsit_code       = nodecodes.whatsit
local math_code          = nodecodes.math
local penalty_code       = nodecodes.penalty
local kern_code          = nodecodes.kern
local glue_code          = nodecodes.glue
local hlist_code         = nodecodes.hlist
local vlist_code         = nodecodes.vlist

local localpar_code      = whatcodes.localpar
local dir_code           = whatcodes.dir

local new_textdir        = nodepool.textdir

local hasbit             = number.hasbit
local formatters         = string.formatters
local insert             = table.insert

local fonthashes         = fonts.hashes
local fontdata           = fonthashes.identifiers
local fontchar           = fonthashes.characters

local chardirections     = characters.directions
local charmirrors        = characters.mirrors
local charclasses        = characters.textclasses

local directions         = typesetters.directions
local setcolor           = directions.setcolor
local getglobal          = directions.getglobal

local a_state            = attributes.private('state')
local a_directions       = attributes.private('directions')

local strip              = false

local s_isol             = fonts.analyzers.states.isol

local function stopdir(finish)
    return new_textdir(finish == "TRT" and "-TRT" or "-TLT")
end

local function startdir(finish)
    return new_textdir(finish == "TRT" and "+TRT" or "+TLT")
end

local function process(start)

    local head     = tonut(start) -- we have a global head

    local current  = head
    local inserted = nil
    local finish   = nil
    local autodir  = 0
    local embedded = 0
    local override = 0
    local pardir   = 0
    local textdir  = 0
    local done     = false
    local finished = nil
    local finidir  = nil
    local stack    = { }
    local top      = 0
    local obsolete = { }
    local lro      = false
    local lro      = false
    local prevattr = false
    local fences   = { }

    local function finish_auto_before()
        head, inserted = insert_node_before(head,current,stopdir(finish))
        finished, finidir, autodir = inserted, finish, 0
        finish, done = nil, true
    end

    local function finish_auto_after()
        head, current = insert_node_after(head,current,stopdir(finish))
        finished, finidir, autodir = current, finish, 0
        finish, done = nil, true
    end

    local function force_auto_left_before(direction)
        if finish then
            head, inserted = insert_node_before(head,current,stopdir(finish))
            finished = inserted
            finidir  = finish
        end
        if embedded >= 0 then
            finish, autodir = "TLT",  1
        else
            finish, autodir = "TRT", -1
        end
        done = true
        if finidir == finish then
            head = remove_node(head,finished,true)
        else
            head, inserted = insert_node_before(head,current,startdir(finish))
        end
    end

    local function force_auto_right_before(direction)
        if finish then
            head, inserted = insert_node_before(head,current,stopdir(finish))
            finished = inserted
            finidir  = finish
        end
        if embedded <= 0 then
            finish, autodir = "TRT", -1
        else
            finish, autodir = "TLT",  1
        end
        done = true
        if finidir == finish then
            head = remove_node(head,finished,true)
        else
            head, inserted = insert_node_before(head,current,startdir(finish))
        end
    end

    local function nextisright(current)
        current = getnext(current)
        local id = getid(current)
        if id == glyph_code then
            local character = getchar(current)
            local direction = chardirections[character]
            return direction == "r" or direction == "al" or direction == "an"
        end
    end

    local function previsright(current)
        current = getprev(current)
        local id = getid(current)
        if id == glyph_code then
            local character = getchar(current)
            local direction = chardirections[character]
            return direction == "r" or direction == "al" or direction == "an"
        end
    end

    while current do
        local id = getid(current)
        if id == math_code then
            current = getnext(end_of_math(getnext(current)))
        else
            local attr = getattr(current,a_directions)
            if attr and attr > 0 and attr ~= prevattr then
                if not getglobal(a) then
                    lro, rlo = false, false
                end
                prevattr = attr
            end
            if id == glyph_code then
                if attr and attr > 0 then
                    local character = getchar(current)
                    local direction = chardirections[character]
                    local reversed  = false
                    if rlo or override > 0 then
                        if direction == "l" then
                            direction = "r"
                            reversed = true
                        end
                    elseif lro or override < 0 then
                        if direction == "r" or direction == "al" then
                            setprop(current,a_state,s_isol)
                            direction = "l"
                            reversed = true
                        end
                    end
                    if direction == "on" then
                        local mirror = charmirrors[character]
                        if mirror and fontchar[getfont(current)][mirror] then
                            local class = charclasses[character]
                            if class == "open" then
                                if nextisright(current) then
                                    if autodir >= 0 then
                                        force_auto_right_before(direction)
                                    end
                                    setfield(current,"char",mirror)
                                    done = true
                                elseif autodir < 0 then
                                    setfield(current,"char",mirror)
                                    done = true
                                else
                                    mirror = false
                                end
                                local fencedir = autodir == 0 and textdir or autodir
                                fences[#fences+1] = fencedir
                            elseif class == "close" and #fences > 0 then
                                local fencedir = fences[#fences]
                                fences[#fences] = nil
                                if fencedir < 0 then
                                    setfield(current,"char",mirror)
                                    done = true
                                    force_auto_right_before(direction)
                                else
                                    mirror = false
                                end
                            elseif autodir < 0 then
                                setfield(current,"char",mirror)
                                done = true
                            else
                                mirror = false
                            end
                        end
                        if trace_directions then
                            setcolor(current,direction,false,mirror)
                        end
                    elseif direction == "l" then
                        if trace_directions then
                            setcolor(current,"l",reversed)
                        end
                        if autodir <= 0 then -- could be option
                            force_auto_left_before(direction)
                        end
                    elseif direction == "r" then
                        if trace_directions then
                            setcolor(current,"r",reversed)
                        end
                        if autodir >= 0 then
                            force_auto_right_before(direction)
                        end
                    elseif direction == "en" then -- european number
                        if trace_directions then
                            setcolor(current,"l")
                        end
                        if autodir <= 0 then -- could be option
                            force_auto_left_before(direction)
                        end
                    elseif direction == "al" then -- arabic number
                        if trace_directions then
                            setcolor(current,"r")
                        end
                        if autodir >= 0 then
                            force_auto_right_before(direction)
                        end
                    elseif direction == "an" then -- arabic number
                        if trace_directions then
                            setcolor(current,"r")
                        end
                        if autodir >= 0 then
                            force_auto_right_before(direction)
                        end
                    elseif direction == "lro" then -- Left-to-Right Override -> right becomes left
                        top = top + 1
                        stack[top] = { override, embedded }
                        override = -1
                        obsolete[#obsolete+1] = current
                    elseif direction == "rlo" then -- Right-to-Left Override -> left becomes right
                        top = top + 1
                        stack[top] = { override, embedded }
                        override = 1
                        obsolete[#obsolete+1] = current
                    elseif direction == "lre" then -- Left-to-Right Embedding -> TLT
                        top = top + 1
                        stack[top] = { override, embedded }
                        embedded = 1
                        obsolete[#obsolete+1] = current
                    elseif direction == "rle" then -- Right-to-Left Embedding -> TRT
                        top = top + 1
                        stack[top] = { override, embedded }
                        embedded = -1
                        obsolete[#obsolete+1] = current
                    elseif direction == "pdf" then -- Pop Directional Format
                        if top > 0 then
                            local s = stack[top]
                            override, embedded = s[1], s[2]
                            top = top - 1
                        end
                        obsolete[#obsolete+1] = current
                    elseif trace_directions then
                        setcolor(current)
                    end
                else
                    -- we do nothing
                end
            elseif id == whatsit_code then
                local subtype = getsubtype(current)
                if subtype == localpar_code then
                    local dir = getfield(current,"dir")
                    if dir == 'TRT' then
                        autodir = -1
                    elseif dir == 'TLT' then
                        autodir = 1
                    end
                    pardir = autodir
                    textdir = pardir
                elseif subtype == dir_code then
                    -- todo: also treat as lro|rlo and stack
                    if finish then
                        finish_auto_before()
                    end
                    local dir = getfield(current,"dir")
                    if dir == "+TRT" then
                        finish, autodir = "TRT", -1
                    elseif dir == "-TRT" then
                        finish, autodir = nil, 0
                    elseif dir == "+TLT" then
                        finish, autodir = "TLT", 1
                    elseif dir == "-TLT" then
                        finish, autodir = nil, 0
                    end
                    textdir = autodir
                else
                    if finish then
                        finish_auto_before()
                    end
                end
            elseif finish then
                finish_auto_before()
            end
            local cn = getnext(current)
            if cn then
                -- we're okay
            elseif finish then
                finish_auto_after()
            end
            current = cn
        end
    end

    if done and strip then
        local n = #obsolete
        if n > 0 then
            for i=1,n do
                remove_node(head,obsolete[i],true)
            end
            report_directions("%s character nodes removed",n)
        end
    end

    return tonode(head), done

end

directions.installhandler(interfaces.variables.default,process)

