require('polyglossia') -- just in case...

local add_to_callback = luatexbase.add_to_callback
local remove_from_callback = luatexbase.remove_from_callback
local priority_in_callback = luatexbase.priority_in_callback

local get_quad = luaotfload.aux.get_quad -- needs luaotfload > 20130516

local next, type = next, type

local nodes, fonts, node = nodes, fonts, node

local nodecodes          = nodes.nodecodes

local insert_node_before = node.insert_before
local insert_node_after  = node.insert_after
local remove_node        = nodes.remove
local has_attribute      = node.has_attribute
local node_copy          = node.copy
local new_node           = node.new

local end_of_math        = node.end_of_math
if not end_of_math then -- luatex < .76
  local traverse_nodes = node.traverse_id
  local math_code      = nodecodes.math
  local end_of_math = function (n)
    for n in traverse_nodes(math_code, n.next) do
      return n
    end
  end
end

-- node types according to node.types()
local glue_code         = nodecodes.glue
local glue_spec_code    = nodecodes.glue_spec
local glyph_code        = nodecodes.glyph
local penalty_code      = nodecodes.penalty
local kern_code         = nodecodes.kern

-- we make a new node, so that we can copy it later on
local penalty_node   = new_node(penalty_code)
penalty_node.penalty = 10000

local function get_penalty_node()
  return node_copy(penalty_node)
end

-- same for glue node
local kern_node       = new_node(kern_code)

local function get_kern_node(dim)
  local n = node_copy(kern_node)
  n.kern = dim
  return n
end

-- we have here all possible space characters, referenced by their
-- unicode slot number, taken from char-def.lua
local space_chars = {[32]=1, [160]=1, [5760]=1, [6158]=1, [8192]=1, [8193]=1, [8194]=1, [8195]=1, 
  [8196]=1, [8197]=1, [8198]=1, [8199]=1, [8200]=1, [8201]=1, [8202]=1, [8239]=1, [8287]=1, [12288]=1}

-- from nodes-tst.lua, adapted
local function somespace(n,all)
    if n then
        local id = n.id
        if id == glue_code then
            return (all or (n.spec.width ~= 0)) and glue_code
        elseif id == kern_code then
            return (all or (n.kern ~= 0)) and kern_code
        elseif id == glyph_code then
            if space_chars[n.char] then
                return true
            else
                return false
            end
        end
    end
    return false
end

-- idem
local function somepenalty(n,value)
    if n then
        local id = n.id
        if id == penalty_code then
            if value then
                return n.penalty == value
            else
                return true
            end
        end
    end
    return false
end

local xpgfrptattr = luatexbase.attributes['xpg@frpt']

local left=1
local right=2
local byte = unicode.utf8.byte

-- Now there is a good question: how do we now, in lua, what a \thinspace is?
-- In the LaTeX source (ltspace.dtx) it is defined as:
-- \def\thinspace{\kern .16667em }. I see no way of seeing if it has been
-- overriden or not... So we stick to this value.
local thinspace  = 0.16667 
-- thickspace is defined in amsmath.sty as:
-- \renewcommand{\;}{\mspace+\thickmuskip{.2777em}}. Same problem as above, we
-- stick to this fixed value.
local thickspace = 0.2777 -- 5/18

local mappings =  {
 [byte(':')] = {left,  thickspace}, --really?
 [byte('!')] = {left,  thinspace},
 [byte('?')] = {left,  thinspace},
 [byte(';')] = {left,  thinspace},
 [byte('‼')] = {left,  thinspace},
 [byte('⁇')] = {left,  thinspace},
 [byte('⁈')] = {left,  thinspace},
 [byte('⁉')] = {left,  thinspace},
 [byte('»')] = {left,  thinspace},
 [byte('>')] = {left,  thinspace},
 [byte('«')] = {right, thinspace}, 
 [byte('‹')] = {right, thinspace}, 
 }

local function set_spacings(thinsp, thicksp)
  for _, m in pairs(mappings) do
    if m[2] == thinspace then
      m[2] = thinsp
    elseif m[2] == thickspace then
      m[2] = thicksp
    end
  end
  thickspace = thicksp
  thinspace = thinsp
end

-- from typo-spa.lua
local function process(head)
    local done = false
    local start = head
    -- head is always begin of par (whatsit), so we have at least two prev nodes
    -- penalty followed by glue
    while start do
        local id = start.id
        if id == glyph_code then
            local attr = has_attribute(start, xpgfrptattr)
            if attr and attr > 0 then
                local char = start.char
                local map = mappings[char]
                --node.unset_attribute(start, xpgfrptattr) -- needed?
                if map then
                    local quad = get_quad(start.font) -- might be optimized
                    local prev = start.prev
                    if map[1] == left and prev then
                        local prevprev = prev.prev
                        local somespace = somespace(prev,true)
                        -- TODO: there is a question here: do we override a preceding space or not?...
                        if somespace then
                            local somepenalty = somepenalty(prevprev,10000)
                            if somepenalty then
                                head = remove_node(head,prev,true)
                                head = remove_node(head,prevprev,true)
                            else
                                head = remove_node(head,prev,true)
                            end
                        end
                        insert_node_before(head,start,get_penalty_node())
                        insert_node_before(head,start,get_kern_node(map[2]*quad))
                        done = true
                    end
                    local next = start.next
                    if map[1] == right and next then
                        local nextnext = next.next
                        local somepenalty = somepenalty(next,10000)
                        if somepenalty then
                            local somespace = somespace(nextnext,true)
                            if somespace then
                                head = remove_node(head,next,true)
                                head = remove_node(head,nextnext,true)
                            end
                        else
                            local somespace = somespace(next,true)
                            if somespace then
                                head = remove_node(head,next,true)
                            end
                        end
                        insert_node_after(head,start,get_kern_node(map[2]*quad))
                        insert_node_after(head,start,get_penalty_node())
                        done = true
                    end
                end
            end
        elseif id == math_code then
            -- warning: this is a feature of luatex > 0.76
            start = end_of_math(start) -- weird, can return nil .. no math end?
        end
        if start then
            start = start.next
        end
    end
    return head, done
end

local callback_name = "pre_linebreak_filter"

local function activate()
  if not priority_in_callback (callback_name, "polyglossia-frpt.process") then
    add_to_callback(callback_name, process, "polyglossia-frpt.process", 1)
  end
end

local function desactivate()
  if priority_in_callback (callback_name, "polyglossia-frpt.process") then
    remove_from_callback(callback_name, "polyglossia-frpt.process")
  end
end

polyglossia.activate_frpt    = activate
polyglossia.desactivate_frpt = desactivate
polyglossia.set_spacings     = set_spacings
polyglossia.thinspace        = thinspace
polyglossia.thickspace       = thickpace
