-- Copyright (c) 2022-2023 Thomas Kelkel kelkel@emaileon.de

-- This file may be distributed and/or modified under the
-- conditions of the LaTeX Project Public License, either
-- version 1.3c of this license or (at your option) any later
-- version. The latest version of this license is in

--    http://www.latex-project.org/lppl.txt

-- and version 1.3c or later is part of all distributions of
-- LaTeX version 2009/09/24 or later.

-- Version: 0.3

local ID = node.id
local GLYPH = ID ( "glyph" )
local DISC = ID ( "disc" )
local GLUE = ID ( "glue" )
local KERN = ID ( "kern" )
local WI = ID ( "whatsit" )
local HLIST = ID ( "hlist" )
local VLIST = ID ( "vlist" )
local INS = ID ( "ins" )

local SWAPPED = table.swapped
local SUBTYPES = node.subtypes
local WIS = node.whatsits
local PDF_LITERAL = SWAPPED ( WIS () )["pdf_literal"]
local LEADERS = SWAPPED ( SUBTYPES ("glue") )["leaders"]
local RIGHTSKIP = SWAPPED ( SUBTYPES ("glue") )["rightskip"]

local NEW = node.new
local COPY = node.copy
local REM = node.remove
local PREV = node.prev
local NEXT = node.next
local INS_B = node.insert_before
local INS_A = node.insert_after
local HAS_GLYPH = node.has_glyph
local T = node.traverse
local T_ID = node.traverse_id
local T_GLYPH = node.traverse_glyph

local FLOOR = math.floor

local GET_FONT = font.getfont

local ATC = luatexbase.add_to_callback

-----

local make = false
local on_top = false
local DIR = PREV
local AB = INS_B
local f = 1
local color = "1 .25 .45"

function make_not_on_top ()
    on_top = false
    DIR = PREV
    AB = INS_B
    f = 1
end

function make_on_top ()
    on_top = true
    DIR = NEXT
    AB = INS_A
    f = - 1
end

local in_use = make_not_on_top

function showhyphenation_make ()
    make = true
end

function showhyphenation_on_top ()
    in_use = make_on_top
    in_use()
end

function showhyphenation_lime ()
    color = ".75 1 .25"
end

local function round ( num, dec )
    return FLOOR ( num * 10^dec + 0.5 ) / 10^dec
end

local function calc_value ( value )
    value = round ( value / 65781, 3 )
    return value
end

local function get_x_value ( n, x_value, minus, half )
    local exp_fac = 1
    local f = 1
    if minus then
        f = - 1
    end
    local ff = 1
    if half then
        ff = 0.5
    end
    if n.expansion_factor then
        exp_fac = n.expansion_factor / 1000000 + 1
    end
    if n.id == GLYPH then
        x_value = x_value + calc_value ( n.width ) * exp_fac * f
    elseif n.id == KERN then
        x_value = x_value + calc_value ( n.kern ) * exp_fac * f * ff
    end
    return x_value
end

local line_end_count = 0

local function find_glyph ( n, d, kern_value, linebreak, lig )
    local line_end = nil
    local ligtype_mark = nil
    local kern_at_disc = 0
    if d ( n ) then
        n = d ( n )
        while not ( n.id == GLYPH and n.char ~= 45 or n.replace and HAS_GLYPH ( n.replace ) ) do
            if n.id == GLUE or n.subtype == LEADERS or n.id == INS then
                return false
            end
            if n.width and not ( n.char and n.char == 45 ) then
                kern_value = get_x_value ( n, kern_value ) -- rules, etc.
            elseif n.kern then
                local factor = 1
                if linebreak or not lig then
                    factor = 0.5 -- hyphen kern
                    kern_at_disc = get_x_value ( n, kern_at_disc ) * factor
                end
                kern_value = get_x_value ( n, kern_value ) * factor
            elseif n.replace then
                for kern_node in T_ID ( KERN, n.replace ) do
                    kern_value = get_x_value ( kern_node, kern_value )
                end
            end
            if n.char == 45 then
                line_end = true
                line_end_count = line_end_count + 1
            end
            if line_end and n.user_id == 848485 then
                ligtype_mark = true
            end
            if d ( n ) then
                n = d ( n )
            else
                return false
            end
        end
        if n.replace then
            local REPLACE = n.replace
            for some_node in T ( REPLACE ) do
                if some_node.width then
                    kern_value = get_x_value ( some_node, kern_value )
                elseif some_node.kern then
                    kern_value = get_x_value ( some_node, kern_value )
                end
            end
        else
            if n.width then
                kern_value = get_x_value ( n, kern_value )
            end
        end
    end
    return n, kern_value, ligtype_mark, kern_at_disc
end

local function check_linebreak ( n, d )
    while n.id ~= GLYPH do
        if d ( n ) then
            n = d ( n )
        else break end
    end
    if n.char == 45 and d == PREV or n.id == GLUE and n.subtype == RIGHTSKIP and d == NEXT then
        make_not_on_top()
        return true
    end
end

local function hyphenation_points ( head )
    for n in T ( head ) do
        if n.id == HLIST or n.id == VLIST then
            n.head = hyphenation_points ( n.head )
        elseif n.id == DISC then
            check_linebreak ( n, PREV )
            check_linebreak ( n, NEXT )
            local lig_add = 0
            local prev_next_kern = 0
            local prev_next_kern_lig = 0
            local pre_kerns = 0
            local second_pre_glyph = 0
            local lig = false
            if n.replace then
                if not HAS_GLYPH ( n.replace ) then
                    local REPLACE = n.replace
                    for some_node in T ( REPLACE ) do
                        if some_node.kern and not check_linebreak ( n, DIR ) then
                            prev_next_kern_lig = get_x_value ( some_node, prev_next_kern_lig ) * 0.5
                        end
                    end
                else
                    local gn_counter = 0
                    for glyph_node in T_GLYPH ( n.replace ) do
                        if glyph_node.components then
                            lig = true
                        end
                    end
                    if n.post then
                        for _ in T_ID ( KERN, n.pre ) do
                            pre_kerns = pre_kerns + 1
                        end
                        for glyph_node in T_GLYPH ( n.pre ) do
                            if glyph_node.width then
                                lig_add = get_x_value ( glyph_node, lig_add )
                                break -- hyphen!
                            end
                        end
                        for glyph_node in T_GLYPH ( n.replace ) do
                            if glyph_node.width then
                                gn_counter = gn_counter + 1
                                if gn_counter == 2 then
                                    second_pre_glyph = get_x_value ( glyph_node, second_pre_glyph, true )
                                end
                            end
                        end
                        if not on_top then
                            if n.replace.kern then
                                prev_next_kern_lig = get_x_value ( n.replace, prev_next_kern_lig )
                            end
                        end
                    else
                        if n.replace.kern then
                            prev_next_kern_lig = get_x_value ( n.replace, prev_next_kern_lig ) * 0.5
                        end
                        for glyph_node in T_GLYPH ( n.replace ) do -- short-armed f
                            if glyph_node.char ~= 45 then
                                lig_add = get_x_value ( glyph_node, lig_add )
                            end
                        end
                    end
                    if on_top then
                        for some_node in T ( n.replace ) do
                            if some_node.width then
                                lig_add = get_x_value ( some_node, lig_add, true )
                            elseif some_node.kern and some_node ~= n.replace then
                                lig_add = get_x_value ( some_node, lig_add, true )
                            end
                        end
                    end
                    if gn_counter > 1 then
                        local counter = 0
                        if not on_top then
                            for kern_node in T_ID ( KERN, n.replace ) do
                                counter = counter + 1
                                if counter > 1 then
                                    if pre_kerns > 1 then
                                        if pre_kerns > 2 then
                                            prev_next_kern_lig = get_x_value ( kern_node, prev_next_kern_lig ) + lig_add
                                        else
                                            prev_next_kern_lig = get_x_value ( kern_node, prev_next_kern_lig, false, true )
                                        end
                                        pre_kerns = pre_kerns - 1
                                    end
                                end
                            end
                        else
                            for kern_node in T_ID ( KERN, n.replace ) do
                                counter = counter + 1
                                if counter > 1 then
                                    if pre_kerns > 1 then
                                        if pre_kerns > 2 then
                                            prev_next_kern_lig = get_x_value ( kern_node, prev_next_kern_lig, true ) + second_pre_glyph
                                        else
                                            prev_next_kern_lig = get_x_value ( kern_node, prev_next_kern_lig, true, true )
                                        end
                                        pre_kerns = pre_kerns - 1
                                    end
                                end
                            end
                        end
                    end
                end
            end
            local prev_next_glyph = n
            local ligtype_mark = nil
            if find_glyph ( prev_next_glyph, DIR, 0 ) then
                prev_next_glyph, prev_next_kern, ligtype_mark = find_glyph ( prev_next_glyph, DIR, 0, check_linebreak ( n, PREV ) or check_linebreak ( n, NEXT ), lig )
            end
            local reverse = PREV
            if DIR == PREV then
                reverse = NEXT
            end
            if find_glyph ( n, reverse, 0 ) then
                local _, __, ___, kern_at_disc  = find_glyph ( n, reverse, 0, check_linebreak ( n, PREV ) or check_linebreak ( n, NEXT ), lig )
                if kern_at_disc then
                    prev_next_kern = prev_next_kern + kern_at_disc
                end
            end
            head = AB ( head, prev_next_glyph, NEW ( WI, PDF_LITERAL ) )
            lig_add = lig_add + ( prev_next_kern + prev_next_kern_lig ) * f
            DIR ( prev_next_glyph ).mode = 0
            local size_factor = 1
            if font.current() then
                size_factor = calc_value ( GET_FONT ( font.current() ).size / 10 )
            end
            local DATA
            if PREV ( n ) and PREV ( n ).user_id and PREV ( n ).user_id == 848485 or ligtype_mark then
                DATA = "q " .. lig_add - 2 * size_factor ..  " " .. -3 * size_factor .. " m " .. lig_add + 2 * size_factor ..  " " .. -3 * size_factor .. " l " .. lig_add + 2 * size_factor .. " " .. -4 * size_factor .. " l " .. lig_add - 2 * size_factor ..  " " .. -4 * size_factor .. " l " .. color .. " rg f Q"
            else
                DATA = "q " .. lig_add ..  " 0 m " .. lig_add + 2 * size_factor ..  " " .. -3 * size_factor .. " l " .. lig_add - 2 * size_factor .. " " .. -3 * size_factor .. " l " .. color .. " rg f Q"
            end
            DIR ( prev_next_glyph ).data = DATA
            in_use()
        end
    end
    return head
end

local function make_hyphens ( head )
    for n in T_ID ( DISC, head ) do
        local REPLACE = n.replace
        for some_node in T ( REPLACE ) do
            REM ( REPLACE, some_node )
        end
        local PRE = n.pre
        for some_node in T_GLYPH ( PRE ) do
            REPLACE = INS_A ( REPLACE, REPLACE, COPY ( some_node ) )
        end
        local POST = n.post
        for some_node in T_GLYPH ( POST ) do
            REPLACE = INS_A ( REPLACE, REPLACE, COPY ( some_node ) )
        end
        n.replace = REPLACE
    end
end

function showhyphenation_start ()
    if make then
        ATC ( "ligaturing", make_hyphens, "make hyphens" )
    else
        ATC ( "post_linebreak_filter", hyphenation_points, "show hyphenation points in postline", 1 )
        ATC ( "hpack_filter", hyphenation_points, "show hyphenation points in hpack" )
    end
end