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

-- beware: this is experimental code and there will be a more
-- generic (attribute value driven) interface too but for the
-- moment this is ok

local utf = unicode.utf8

local set_attribute  = node.set_attribute
local has_attribute  = node.has_attribute
local mlist_to_hlist = node.mlist_to_hlist
local font_of_family = node.family_font
local fontdata       = fonts.identifiers

local format, rep  = string.format, string.rep
local utfchar, utfbyte = utf.char, utf.byte

noads = noads or { }

local trace_remapping  = false  trackers.register("math.remapping",  function(v) trace_remapping  = v end)
local trace_processing = false  trackers.register("math.processing", function(v) trace_processing = v end)
local trace_analyzing  = false  trackers.register("math.analyzing",  function(v) trace_analyzing  = v end)

local noad_ord               =  0
local noad_op_displaylimits  =  1
local noad_op_limits         =  2
local noad_op_nolimits       =  3
local noad_bin               =  4
local noad_rel               =  5
local noad_open              =  6
local noad_close             =  7
local noad_punct             =  8
local noad_inner             =  9
local noad_under             = 10
local noad_over              = 11
local noad_vcenter           = 12

-- obsolete:
--
--    math_ord       = node.id("ord")            -- attr nucleus sub sup
--    math_op        = node.id("op")             -- attr nucleus sub sup subtype
--    math_bin       = node.id("bin")            -- attr nucleus sub sup
--    math_rel       = node.id("rel")            -- attr nucleus sub sup
--    math_punct     = node.id("punct")          -- attr nucleus sub sup
--
--    math_open      = node.id("open")           -- attr nucleus sub sup
--    math_close     = node.id("close")          -- attr nucleus sub sup
--
--    math_inner     = node.id("inner")          -- attr nucleus sub sup
--    math_vcenter   = node.id("vcenter")        -- attr nucleus sub sup
--    math_under     = node.id("under")          -- attr nucleus sub sup
--    math_over      = node.id("over")           -- attr nucleus sub sup

local math_noad      = node.id("noad")           -- attr nucleus sub sup

local math_accent    = node.id("accent")         -- attr nucleus sub sup accent
local math_radical   = node.id("radical")        -- attr nucleus sub sup left degree
local math_fraction  = node.id("fraction")       -- attr nucleus sub sup left right

local math_box       = node.id("sub_box")        -- attr list
local math_sub       = node.id("sub_mlist")      -- attr list
local math_char      = node.id("math_char")      -- attr fam char
local math_text_char = node.id("math_text_char") -- attr fam char
local math_delim     = node.id("delim")          -- attr small_fam small_char large_fam large_char
local math_style     = node.id("style")          -- attr style
local math_choice    = node.id("choice")         -- attr display text script scriptscript
local math_fence     = node.id("fence")          -- attr subtype

local simple_noads = table.tohash {
    math_noad,
}

local all_noads = {
    math_noad,
    math_box, math_sub,
    math_char, math_text_char, math_delim, math_style,
    math_accent, math_radical, math_fraction, math_choice, math_fence,
}

noads.processors = noads.processors or { }

local function process(start,what,n)
    if n then n = n + 1 else n = 0 end
    while start do
        if trace_processing then
            logs.report("math","%s%s",rep("  ",n or 0),tostring(start))
        end
        local id = start.id
        local proc = what[id]
        if proc then
            proc(start,what,n)
        elseif id == math_char or id == math_text_char or id == math_delim then
            break
        elseif id == math_style then
            -- has a next
        elseif id == math_noad then
            local noad = start.nucleus      if noad then process(noad,what,n) end -- list
                  noad = start.sup          if noad then process(noad,what,n) end -- list
                  noad = start.sub          if noad then process(noad,what,n) end -- list
        elseif id == math_box or id == math_sub then
            local noad = start.list         if noad then process(noad,what,n) end -- list
        elseif id == math_fraction then
            local noad = start.num          if noad then process(noad,what,n) end -- list
                  noad = start.denom        if noad then process(noad,what,n) end -- list
                  noad = start.left         if noad then process(noad,what,n) end -- delimiter
                  noad = start.right        if noad then process(noad,what,n) end -- delimiter
        elseif id == math_choice then
            local noad = start.display      if noad then process(noad,what,n) end -- list
                  noad = start.text         if noad then process(noad,what,n) end -- list
                  noad = start.script       if noad then process(noad,what,n) end -- list
                  noad = start.scriptscript if noad then process(noad,what,n) end -- list
        elseif id == math_fence then
            local noad = start.delim        if noad then process(noad,what,n) end -- delimiter
        elseif id == math_radical then
            local noad = start.nucleus      if noad then process(noad,what,n) end -- list
                  noad = start.sup          if noad then process(noad,what,n) end -- list
                  noad = start.sub          if noad then process(noad,what,n) end -- list
                  noad = start.left         if noad then process(noad,what,n) end -- delimiter
                  noad = start.degree       if noad then process(noad,what,n) end -- list
        elseif id == math_accent then
            local noad = start.nucleus      if noad then process(noad,what,n) end -- list
                  noad = start.sup          if noad then process(noad,what,n) end -- list
                  noad = start.sub          if noad then process(noad,what,n) end -- list
                  noad = start.accent       if noad then process(noad,what,n) end -- list
                  noad = start.bot_accent   if noad then process(noad,what,n) end -- list
        else
            -- glue, penalty, etc
        end
        start = start.next
    end
end

noads.process = process

-- character remapping

local mathalphabet = attributes.private("mathalphabet")
local mathgreek    = attributes.private("mathgreek")

noads.processors.relocate = { }

local function report_remap(tag,id,old,new,extra)
    logs.report("math","remapping %s in font %s from U+%04X (%s) to U+%04X (%s)%s",tag,id,old,utfchar(old),new,utfchar(new),extra or "")
end

local remap_alphabets = mathematics.remap_alphabets
local fcs = fonts.color.set

-- we can have a global famdata == fonts.famdata and chrdata == fonts.chrdata

--~ This does not work out well, as there are no fallbacks. Ok, we could
--~ define a poor mans simplify mechanism.
--~
--~ local function checked(pointer)
--~     local char = pointer.char
--~     local fam = pointer.fam
--~     local id = font_of_family(fam)
--~     local tfmdata = fontdata[id]
--~     local tc = tfmdata and tfmdata.characters
--~     if not tc[char] then
--~         local specials = characters.data[char].specials
--~         if specials and (specials[1] == "char" or specials[1] == "font") then
--~             newchar = specials[#specials]
--~             if trace_remapping then
--~                 report_remap("fallback",id,char,newchar)
--~             end
--~             if trace_analyzing then
--~                 fcs(pointer,"font:isol")
--~             end
--~             pointer.char = newchar
--~             return true
--~         end
--~     end
--~ end

noads.processors.relocate[math_char] = function(pointer)
    local g = has_attribute(pointer,mathgreek) or 0
    local a = has_attribute(pointer,mathalphabet) or 0
    if a > 0 or g > 0 then
        if a > 0 then
            set_attribute(pointer,mathgreek,0)
        end
        if g > 0 then
            set_attribute(pointer,mathalphabet,0)
        end
        local char = pointer.char
        local newchar = remap_alphabets(char,a,g)
        if newchar then
            local fam = pointer.fam
            local id = font_of_family(fam)
            local tfmdata = fontdata[id]
            if tfmdata and tfmdata.characters[newchar] then -- we could probably speed this up
                if trace_remapping then
                    report_remap("char",id,char,newchar)
                end
                if trace_analyzing then
                    fcs(pointer,"font:isol")
                end
                pointer.char = newchar
                return true
            elseif trace_remapping then
                report_remap("char",id,char,newchar," fails")
            end
        else
            -- return checked(pointer)
        end
    else
        -- return checked(pointer)
    end
    if trace_analyzing then
        fcs(pointer,"font:medi")
    end
end

noads.processors.relocate[math_text_char] = function(pointer)
    if trace_analyzing then
        fcs(pointer,"font:init")
    end
end

noads.processors.relocate[math_delim] = function(pointer)
    if trace_analyzing then
        fcs(pointer,"font:fina")
    end
end

function noads.relocate_characters(head,style,penalties)
    process(head,noads.processors.relocate)
    return true
end

-- some resize options (this works ok because the content is
-- empty and no larger next will be forced)
--
-- beware: we don't use \delcode but \Udelcode and as such have
-- no large_fam; also, we need to check for subtype and/or
-- small_fam not being 0 because \. sits in 0,0 by default
--
-- todo: just replace the character by an ord noad
-- and remove the right delimiter as well

local mathsize = attributes.private("mathsize")

noads.processors.resize = { }

noads.processors.resize[math_fence] = function(pointer)
    if pointer.subtype == 1 then -- left
        local a = has_attribute(pointer,mathsize)
        if a and a > 0 then
            set_attribute(pointer,mathsize,0)
            local d = pointer.delim
            local df = d.small_fam
            local id = font_of_family(df)
            if id > 0 then
                local ch = d.small_char
                d.small_char = mathematics.big(fontdata[id],ch,a)
            end
        end
    end
end

function noads.resize_characters(head,style,penalties)
    process(head,noads.processors.resize)
    return true
end

-- respacing

local mathpunctuation = attributes.private("mathpunctuation")

noads.processors.respace = { }

local chardata = characters.data

-- only [nd,ll,ul][po][nd,ll,ul]

noads.processors.respace[math_noad] = function(pointer)
    if pointer.subtype == noad_ord then
        local a = has_attribute(pointer,mathpunctuation)
        if a and a > 0 then
            set_attribute(pointer,mathpunctuation,0)
            local current_nucleus = pointer.nucleus
            if current_nucleus.id == math_char then
                local current_char = current_nucleus.char
                local fc = chardata[current_char]
                fc = fc and fc.category
                if fc == "nd" or fc == "ll" or fc == "lu" then
                    local next_noad = pointer.next
                    if next_noad and next_noad.id == math_noad and next_noad.subtype == noad_punct then
                        local next_nucleus = next_noad.nucleus
                        if next_nucleus.id == math_char then
                            local next_char = next_nucleus.char
                            local nc = chardata[next_char]
                            nc = nc and nc.category
                            if nc == "po" then
                                local last_noad = next_noad.next
                                if last_noad and last_noad.id == math_noad and last_noad.subtype == noad_ord then
                                    local last_nucleus = last_noad.nucleus
                                    if last_nucleus.id == math_char then
                                        local last_char = last_nucleus.char
                                        local lc = chardata[last_char]
                                        lc = lc and lc.category
                                        if lc == "nd" or lc == "ll" or lc == "lu" then
                                            local ord = node.new(math_noad) -- todo: pool
                                            ord.subtype, ord.nucleus, ord.sub, ord.sup, ord.attr = noad_ord, next_noad.nucleus, next_noad.sub, next_noad.sup, next_noad.attr
                                        --  next_noad.nucleus, next_noad.sub, next_noad.sup, next_noad.attr = nil, nil, nil, nil
                                            next_noad.nucleus, next_noad.sub, next_noad.sup = nil, nil, nil -- else crash with attributes ref count
                                        --~ next_noad.attr = nil
                                            ord.next = last_noad
                                            pointer.next = ord
                                            node.free(next_noad)
                                        end
                                    end
                                end
                            end
                        end
                    end
                end
            end
        end
    end
end


function noads.respace_characters(head,style,penalties)
    noads.process(head,noads.processors.respace)
    return true
end

-- the normal builder

function noads.mlist_to_hlist(head,style,penalties)
    return mlist_to_hlist(head,style,penalties), true
end

tasks.new (
    "math",
    {
        "before",
        "normalizers",
        "builders",
        "after",
    }
)

local actions = tasks.actions("math",2) -- head, style, penalties

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

function nodes.processors.mlist_to_hlist(head,style,penalties)
    starttiming(noads)
    local head, done = actions(head,style,penalties)
    stoptiming(noads)
    return head, done
end

callbacks.register('mlist_to_hlist',nodes.processors.mlist_to_hlist,"preprocessing math list")

-- tracing

statistics.register("math processing time", function()
    return statistics.elapsedseconds(noads)
end)
