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

-- this code dates from the beginning and is kind of experimental; it
-- will be optimized and improved soon

local next, type, tonumber = next, type, tonumber
local utfbyte, utfchar = utf.byte, utf.char
local format = string.format

local trace_breakpoints = false  trackers.register("typesetters.breakpoints", function(v) trace_breakpoints = v end)

local report_breakpoints = logs.reporter("typesetting","breakpoints")

local nodes, node = nodes, node

local settings_to_array  = utilities.parsers.settings_to_array

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

local getnext            = nuts.getnext
local getprev            = nuts.getprev
local getsubtype         = nuts.getsubtype
local getchar            = nuts.getchar
local getfont            = nuts.getfont
local getid              = nuts.getid
local getfield           = nuts.getfield
local setfield           = nuts.setfield
local getattr            = nuts.getattr
local setattr            = nuts.setattr

local copy_node          = nuts.copy
local copy_nodelist      = nuts.copy_list
local free_node          = nuts.free
local insert_node_before = nuts.insert_before
local insert_node_after  = nuts.insert_after
local remove_node        = nuts.remove

local tonodes            = nuts.tonodes

local texsetattribute    = tex.setattribute
local unsetvalue         = attributes.unsetvalue

local nodepool           = nuts.pool
local tasks              = nodes.tasks

local v_reset            = interfaces.variables.reset

local implement          = interfaces.implement

local new_penalty        = nodepool.penalty
local new_glue           = nodepool.glue
local new_disc           = nodepool.disc

local nodecodes          = nodes.nodecodes
local kerncodes          = nodes.kerncodes

local glyph_code         = nodecodes.glyph
local kern_code          = nodecodes.kern

local kerning_code       = kerncodes.kerning

local typesetters        = typesetters

typesetters.breakpoints  = typesetters.breakpoints or {}
local breakpoints        = typesetters.breakpoints

breakpoints.mapping      = breakpoints.mapping or { }
breakpoints.numbers      = breakpoints.numbers or { }

breakpoints.methods      = breakpoints.methods or { }
local methods            = breakpoints.methods

local a_breakpoints      = attributes.private("breakpoint")

storage.register("typesetters/breakpoints/mapping", breakpoints.mapping, "typesetters.breakpoints.mapping")

local mapping            = breakpoints.mapping
local numbers            = breakpoints.mapping

for i=1,#mapping do
    local m = mapping[i]
    numbers[m.name] = m
end

local function insert_break(head,start,before,after)
    insert_node_before(head,start,new_penalty(before))
    insert_node_before(head,start,new_glue(0))
    insert_node_after(head,start,new_glue(0))
    insert_node_after(head,start,new_penalty(after))
end

methods[1] = function(head,start)
    if getprev(start) and getnext(start) then
        insert_break(head,start,10000,0)
    end
    return head, start
end

methods[2] = function(head,start) -- ( => (-
    if getprev(start) and getnext(start) then
        local tmp
        head, start, tmp = remove_node(head,start)
        head, start = insert_node_before(head,start,new_disc())
     -- setfield(start,"attr",copy_nodelist(getfield(tmp,"attr"))) -- just a copy will do
        setfield(start,"attr",getfield(tmp,"attr"))
        setfield(start,"replace",tmp)
        local tmp = copy_node(tmp)
        local hyphen = copy_node(tmp)
        setfield(hyphen,"char",languages.prehyphenchar(getfield(tmp,"lang")))
        setfield(tmp,"next",hyphen)
        setfield(hyphen,"prev",tmp)
        setfield(start,"post",tmp)
        insert_break(head,start,10000,10000)
    end
    return head, start
end

methods[3] = function(head,start) -- ) => -)
    if getprev(start) and getnext(start) then
        local tmp
        head, start, tmp = remove_node(head,start)
        head, start = insert_node_before(head,start,new_disc())
     -- setfield(start,"attr",copy_nodelist(getfield(tmp,"attr"))) -- just a copy will do
        setfield(start,"attr",getfield(tmp,"attr"))
        setfield(start,"replace",tmp)
        local tmp = copy_node(tmp)
        local hyphen = copy_node(tmp)
        setfield(hyphen,"char",languages.prehyphenchar(getfield(tmp,"lang")))
        setfield(tmp,"prev",hyphen)
        setfield(hyphen,"next",tmp)
        setfield(start,"pre",hyphen)
        insert_break(head,start,10000,10000)
    end
    return head, start
end

methods[4] = function(head,start) -- - => - - -
    if getprev(start) and getnext(start) then
        local tmp
        head, start, tmp = remove_node(head,start)
        head, start = insert_node_before(head,start,new_disc())
     -- setfield(start,"attr",copy_nodelist(getfield(tmp,"attr"))) -- just a copy will do
        setfield(start,"attr",getfield(tmp,"attr"))
        setfield(start,"pre",copy_node(tmp))
        setfield(start,"post",copy_node(tmp))
        setfield(start,"replace",tmp)
        insert_break(head,start,10000,10000)
    end
    return head, start
end

methods[5] = function(head,start,settings) -- x => p q r
    if getprev(start) and getnext(start) then
        local tmp
        head, start, tmp = remove_node(head,start)
        head, start = insert_node_before(head,start,new_disc())
        local attr = getfield(tmp,"attr")
        local font = getfont(tmp)
        local left = settings.left
        local right = settings.right
        local middle = settings.middle
        if left then
            setfield(start,"pre",(tonodes(tostring(left),font,attr))) -- was right
        end
        if right then
            setfield(start,"post",(tonodes(tostring(right),font,attr))) -- was left
        end
        if middle then
            setfield(start,"replace",(tonodes(tostring(middle),font,attr)))
        end
     -- setfield(start,"attr",copy_nodelist(attr)) -- todo: critical only -- just a copy will do
        setfield(start,"attr",attr) -- todo: critical only -- just a copy will do
        free_node(tmp)
        insert_break(head,start,10000,10000)
    end
    return head, start
end

function breakpoints.handler(head)
    head = tonut(head)
    local done, numbers = false, languages.numbers
    local start, n = head, 0
    while start do
        local id = getid(start)
        if id == glyph_code then
            local attr = getattr(start,a_breakpoints)
            if attr and attr > 0 then
                setattr(start,a_breakpoints,unsetvalue) -- maybe test for subtype > 256 (faster)
                -- look ahead and back n chars
                local data = mapping[attr]
                if data then
                    local map = data.characters
                    local cmap = map[getchar(start)]
                    if cmap then
                        local lang = getfield(start,"lang")
                        -- we do a sanity check for language
                        local smap = lang and lang >= 0 and lang < 0x7FFF and (cmap[numbers[lang]] or cmap[""])
                        if smap then
                            if n >= smap.nleft then
                                local m = smap.nright
                                local next = getnext(start)
                                while next do -- gamble on same attribute (not that important actually)
                                    local id = getid(next)
                                    if id == glyph_code then -- gamble on same attribute (not that important actually)
                                        if map[getchar(next)] then
                                            break
                                        elseif m == 1 then
                                            local method = methods[smap.type]
                                            if method then
                                                head, start = method(head,start,smap)
                                                done = true
                                            end
                                            break
                                        else
                                            m = m - 1
                                            next = getnext(next)
                                        end
                                    elseif id == kern_code and getsubtype(next) == kerning_code then
                                        next = getnext(next)
                                        -- ignore intercharacter kerning, will go way
                                    else
                                        -- we can do clever and set n and jump ahead but ... not now
                                        break
                                    end
                                end
                            end
                            n = 0
                        else
                            n = n + 1
                        end
                    else
                         n = n + 1
                    end
                else
                    n = 0
                end
            else
             -- n = n + 1 -- if we want single char handling (|-|) then we will use grouping and then we need this
            end
        elseif id == kern_code and getsubtype(start) == kerning_code then
            -- ignore intercharacter kerning, will go way
        else
            n = 0
        end
        start = getnext(start)
    end
    return tonode(head), done
end

local enabled = false

function breakpoints.define(name)
    local data = numbers[name]
    if data then
        -- error
    else
        local number = #mapping + 1
        local data = {
            name       = name,
            number     = number,
            characters = { },
        }
        mapping[number] = data
        numbers[name]   = data
    end
end

function breakpoints.setreplacement(name,char,language,settings)
    char = utfbyte(char)
    local data = numbers[name]
    if data then
        local characters = data.characters
        local cmap = characters[char]
        if not cmap then
            cmap = { }
            characters[char] = cmap
        end
        local left, right, middle = settings.left, settings.right, settings.middle
        cmap[language or ""] = {
            type   = tonumber(settings.type)   or 1,
            nleft  = tonumber(settings.nleft)  or 1,
            nright = tonumber(settings.nright) or 1,
            left   = left   ~= "" and left     or nil,
            right  = right  ~= "" and right    or nil,
            middle = middle ~= "" and middle   or nil,
        } -- was { type or 1, before or 1, after or 1 }
    end
end

function breakpoints.set(n)
    if n == v_reset then
        n = unsetvalue
    else
        n = mapping[n]
        if not n then
            n = unsetvalue
        else
            if not enabled then
                if trace_breakpoints then
                    report_breakpoints("enabling breakpoints handler")
                end
                tasks.enableaction("processors","typesetters.breakpoints.handler")
            end
            n = n.number
        end
    end
    texsetattribute(a_breakpoints,n)
end

-- function breakpoints.enable()
--     tasks.enableaction("processors","typesetters.breakpoints.handler")
-- end

-- interface

implement {
    name      = "definebreakpoints",
    actions   = breakpoints.define,
    arguments = "string"
}

implement {
    name      = "definebreakpoint",
    actions   = breakpoints.setreplacement,
    arguments = {
        "string",
        "string",
        "string",
        {
            { "type", "integer" },
            { "nleft" },
            { "nright" },
            { "right" },
            { "left" },
            { "middle" },
        }
    }
}

implement {
    name      = "setbreakpoints",
    actions   = breakpoints.set,
    arguments = "string"
}
