/*
    See license.txt in the root of this project.
*/

# include "luametatex.h"

/*tex

    A |disc_node|, which occurs only in horizontal lists, specifies a \quote {discretionary}
    line break. If such a break occurs at node |p|, the text that starts at |pre_break(p)| will
    precede the break, the text that starts at |post_break(p)| will follow the break, and text
    that appears in |no_break(p)| nodes will be ignored. For example, an ordinary discretionary
    hyphen, indicated by |\-|, yields a |disc_node| with |pre_break| pointing to a |char_node|
    containing a hyphen, |post_break = null|, and |no_break=null|.

    If |subtype(p) = automatic_disc|, the |ex_hyphen_penalty| will be charged for this break.
    Otherwise the |hyphen_penalty| will be charged. The texts will actually be substituted into
    the list by the line-breaking algorithm if it decides to make the break, and the discretionary
    node will disappear at that time; thus, the output routine sees only discretionaries that were
    not chosen.

*/

halfword tex_new_disc_node(quarterword s)
{
    halfword p = tex_new_node(disc_node, s);
    disc_penalty(p) = hyphen_penalty_par;
    disc_class(p) = unset_disc_class;
    set_disc_options(p, discretionary_options_par);
    return p;
}
void tex_set_disc_field(halfword target, halfword location, halfword source)
{
    switch (location) {
        case pre_break_code:  target = disc_pre_break(target);  break;
        case post_break_code: target = disc_post_break(target); break;
        case no_break_code:   target = disc_no_break(target);   break;
    }
    if (source) {
        node_prev(source) = null; /* don't expose this one! */
        node_head(target) = source;
        node_tail(target) = tex_tail_of_node_list(source);
    } else {
        node_head(target) = null;
        node_tail(target) = null;
    }
}

void tex_check_disc_field(halfword n)
{
    halfword p = disc_pre_break_head(n);
    disc_pre_break_tail(n) = p ? tex_tail_of_node_list(p) : null;
    p = disc_post_break_head(n);
    disc_post_break_tail(n) = p ? tex_tail_of_node_list(p) : null;
    p = disc_no_break_head(n);
    disc_no_break_tail(n) = p ? tex_tail_of_node_list(p) : null;
}

void tex_set_discpart(halfword d, halfword h, halfword t, halfword code)
{
    halfword c = h;
    switch (node_subtype(d)) {
        case automatic_discretionary_code:
        case mathematics_discretionary_code:
            code = glyph_discpart_always;
            break;
    }
    while (c) {
        if (node_type(c) == glyph_node) {
            set_glyph_discpart(c, code);
        }
        if (c == t) {
            break;
        } else {
            c = node_next(c);
        }
    }
}

static int tex_set_discafter(halfword h, halfword t, halfword after)
{
    halfword c = h;
    while (c) {
        if (node_type(c) == glyph_node) {
            set_glyph_discafter(c, after);
            return 1;
        }
        if (c == t) {
            break;
        } else {
            c = node_next(c);
        }
    }
    return 0;
}

/*

glyph disc (pre post replace) glyph
disc (pre post replace) glyph
disc (pre post replace) disc

*/

halfword tex_flatten_discretionaries(halfword head, int *count, int nest)
{
    halfword current = head;
    halfword after = 0;
    while (current) {
        halfword next = node_next(current);
        switch (node_type(current)) {
            case disc_node:
                {
                    halfword d = current;
                    halfword h = disc_no_break_head(d);
                    halfword t = disc_no_break_tail(d);
                    after = node_subtype(current) + 1;
                    if (h) {
                        tex_set_discpart(current, h, t, glyph_discpart_replace);
                        tex_try_couple_nodes(t, next);
                        if (current == head) {
                            head = h;
                        } else {
                            tex_try_couple_nodes(node_prev(current), h);
                        }
                        disc_no_break_head(d) = null;
                        if (tex_set_discafter(h, t, after)) {
                            after = 0;
                        }
                    } else if (current == head) {
                        head = next;
                    } else {
                        tex_try_couple_nodes(node_prev(current), next);
                    }
                    tex_flush_node(d);
                    if (count) {
                        *count += 1;
                    }
                    break;
                }
            case glyph_node:
                if (after) {
                    set_glyph_discafter(current, after);
                    after = 0;
                }
                break;
            case hlist_node:
            case vlist_node:
                if (nest) {
                    halfword list = box_list(current);
                    if (list) {
                        box_list(current) = tex_flatten_discretionaries(list, count, nest);
                    }
                }
                break;
            case kern_node:
                switch (node_subtype(current)) {
                    case font_kern_subtype:
                        break;
                    default:
                        after = 0;
                        break;
                }
                break;
            case penalty_node:
            case boundary_node:
                /* maybe some more */
                break;
            default:
                after = 0;
                break;

        }
        current = next;
    }
    return head;
}

/*tex

    Discretionary nodes are easy in the common case |\-|, but in the general case we must process
    three braces full of items.

    The space factor does not change when we append a discretionary node, but it starts out as 1000
    in the subsidiary lists.

*/

typedef enum saved_discretionary_entries {
    saved_discretionary_component_entry = 0, /* value_1 */
    saved_discretionary_n_of_records    = 1,
} saved_discretionary_entries;

# define saved_discretionary_component saved_value_1(saved_discretionary_component_entry)

static inline void saved_discretionary_initialize(void)
{
    saved_type(0) = saved_record_0;
    saved_record(0) = discretionary_save_type;
}

static inline int saved_discretionary_current_component(void)
{
    return saved_type(saved_discretionary_component_entry - saved_discretionary_n_of_records) == saved_record_0 
        ? saved_value_1(saved_discretionary_component_entry - saved_discretionary_n_of_records) : -1 ;
}

static inline void saved_discretionary_update_component(void)
{
    saved_value_1(saved_discretionary_component_entry - saved_discretionary_n_of_records) += 1;
}

void tex_show_discretionary_group(void)
{
    tex_print_str_esc("discretionary");
    tex_aux_show_group_count(saved_discretionary_component);
}

int tex_show_discretionary_record(void)
{
    tex_print_str("discretionary ");
    switch (save_type(lmt_save_state.save_stack_data.ptr)) { 
       case saved_record_0:
            tex_print_format("component %i", saved_discretionary_component);
            break;
        default: 
            return 0;
    }
    return 1;
}

void tex_run_discretionary(void)
{
    switch (cur_chr) {
        case normal_discretionary_code:
            /*tex |\discretionary| */
            {
                halfword d = tex_new_disc_node(normal_discretionary_code);
                tex_tail_append(d);
                while (1) {
                    switch (tex_scan_character("pocbnsPOCBNS", 0, 1, 0)) {
                        case 0:
                            goto DONE;
                        case 'p': case 'P':
                            switch (tex_scan_character("eorEOR", 0, 0, 0)) {
                                case 'e': case 'E':
                                    if (tex_scan_mandate_keyword("penalty", 2)) {
                                        set_disc_penalty(d, tex_scan_integer(0, NULL, NULL));
                                    }
                                    break;
                                case 'o': case 'O':
                                    if (tex_scan_mandate_keyword("postword", 2)) {
                                        set_disc_option(d, disc_option_post_word);
                                    }
                                    break;
                                case 'r': case 'R':
                                    if (tex_scan_mandate_keyword("preword", 2)) {
                                        set_disc_option(d, disc_option_pre_word);
                                    }
                                    break;
                                default:
                                    tex_aux_show_keyword_error("penalty|postword|preword");
                                    goto DONE;
                            }
                            break;
                        case 'b': case 'B':
                            if (tex_scan_mandate_keyword("break", 1)) {
                                set_disc_option(d, disc_option_prefer_break);
                            }
                            break;
                        case 'n': case 'N':
                            if (tex_scan_mandate_keyword("nobreak", 1)) {
                                set_disc_option(d, disc_option_prefer_nobreak);
                            }
                            break;
                        case 'o': case 'O':
                            if (tex_scan_mandate_keyword("options", 1)) {
                                set_disc_options(d, tex_scan_integer(0, NULL, NULL));
                            }
                            break;
                        case 'c': case 'C':
                            if (tex_scan_mandate_keyword("class", 1)) {
                                set_disc_class(d, tex_scan_math_class_number(0));
                            }
                            break;
                        case 's': case 'S':
                            if (tex_scan_mandate_keyword("standalone", 1)) {
                                set_disc_option(d, disc_option_stand_alone);
                            }
                            break;
                        default:
                            goto DONE;
                    }
                }
            DONE:
                saved_discretionary_initialize();
                saved_discretionary_component = 0;
                lmt_save_state.save_stack_data.ptr += saved_discretionary_n_of_records;
                tex_new_save_level(discretionary_group);
                tex_scan_left_brace();
                tex_push_nest();
                cur_list.mode = restricted_hmode;
                cur_list.space_factor = default_space_factor; /* hm, quite hard coded */
                cur_list.space_penalty = 0;
            }
            break;
        case explicit_discretionary_code:
            /*tex |\-| */
            if (hyphenation_permitted(hyphenation_mode_par, explicit_hyphenation_mode)) {
                int c = tex_get_pre_hyphen_char(cur_lang_par);
                halfword d = tex_new_disc_node(explicit_discretionary_code);
                tex_tail_append(d);
                if (c > 0) {
                    halfword g = tex_new_char_node(glyph_unset_subtype, cur_font_par, c, 1);
                    set_glyph_disccode(g, glyph_disc_explicit);
                    tex_set_disc_field(d, pre_break_code, g);
                }
                c = tex_get_post_hyphen_char(cur_lang_par);
                if (c > 0) {
                    halfword g = tex_new_char_node(glyph_unset_subtype, cur_font_par, c, 1);
                    set_glyph_disccode(g, glyph_disc_explicit);
                    tex_set_disc_field(d, post_break_code, g);
                }
                disc_penalty(d) = tex_explicit_disc_penalty(hyphenation_mode_par);
            }
            break;
        case automatic_discretionary_code:
        case mathematics_discretionary_code:
            /*tex |-| */
            if (hyphenation_permitted(hyphenation_mode_par, automatic_hyphenation_mode)) {
                halfword c = tex_get_pre_exhyphen_char(cur_lang_par);
                halfword d = tex_new_disc_node(automatic_discretionary_code);
                halfword f = cur_chr == mathematics_discretionary_code ? glyph_disc_mathematics : glyph_disc_automatic;
                tex_tail_append(d);
                /*tex As done in hyphenator: */
                if (c <= 0) {
                    c = ex_hyphen_char_par;
                }
                if (c > 0) {
                    halfword g = tex_new_char_node(glyph_unset_subtype, cur_font_par, c, 1);
                    set_glyph_disccode(g, f);
                    tex_set_disc_field(d, pre_break_code, g);
                }
                c = tex_get_post_exhyphen_char(cur_lang_par);
                if (c > 0) {
                    halfword g = tex_new_char_node(glyph_unset_subtype, cur_font_par, c, 1);
                    set_glyph_disccode(g, f);
                    tex_set_disc_field(d, post_break_code, g);
                }
                c = ex_hyphen_char_par;
                if (c > 0) {
                    halfword g = tex_new_char_node(glyph_unset_subtype, cur_font_par, c, 1);
                    set_glyph_disccode(g, f);
                    tex_set_disc_field(d, no_break_code, g);
                }
                disc_penalty(d) = tex_automatic_disc_penalty(hyphenation_mode_par);
            } else {
                halfword c = ex_hyphen_char_par;
                if (c > 0) {
                    halfword g = tex_new_char_node(glyph_unset_subtype, cur_font_par, c, 1);
                    set_glyph_discpart(g, glyph_discpart_always);
                    set_glyph_disccode(g, glyph_disc_normal);
                    tex_tail_append(g);
                }
            }
            break;
    }
}

/*tex

    The three discretionary lists are constructed somewhat as if they were hboxes. A subroutine
    called |finish_discretionary| handles the transitions. (This is sort of fun.)

*/

void tex_finish_discretionary(void)
{
    halfword current, next;
    int length = 0;
    tex_unsave();
    /*tex
        Prune the current list, if necessary, until it contains only |char_node|, |kern_node|,
        |hlist_node|, |vlist_node| and |rule_node| items; set |n| to the length of the list, and
        set |q| to the lists tail. During this loop, |p = node_next(q)| and there are |n| items
        preceding |p|.
    */
    current = cur_list.head;
    next = node_next(current);
    while (next) {
        switch (node_type(next)) {
            case glyph_node:
            case hlist_node:
            case vlist_node:
            case rule_node:
            case kern_node:
                break;
            case glue_node:
                if (hyphenation_permitted(hyphenation_mode_par, permit_glue_hyphenation_mode)) {
                    if (glue_stretch_order(next)) {
                        glue_stretch(next) = 0;
                        glue_stretch_order(next) = 0;
                    }
                    if (glue_shrink_order(next)) {
                        glue_shrink(next) = 0;
                        glue_shrink_order(next) = 0;
                    }
                    break;
                } else {
                    // fall through
                }
            default:
                if (hyphenation_permitted(hyphenation_mode_par, permit_all_hyphenation_mode)) {
                    break;
                } else {
                    tex_handle_error(
                        normal_error_type,
                        "Improper discretionary list",
                        "Discretionary lists must contain only glyphs, boxes, rules and kerns."
                    );
                    tex_begin_diagnostic();
                    tex_print_format("%lThe following discretionary sublist has been deleted:");
                    tex_print_levels();
                    tex_show_box(next);
                    tex_end_diagnostic();
                    tex_flush_node_list(next);
                    node_next(current) = null;
                    goto DONE;
                }
        }
        node_prev(next) = current;
        current = next;
        next = node_next(current);
        ++length;
    }
  DONE:
    next = node_next(cur_list.head);
    tex_pop_nest();
    {
        halfword discnode = cur_list.tail;

        if (next) {
            node_prev(next) = null;
            if (node_subtype(disc_node) == normal_discretionary_code && has_disc_option(discnode, disc_option_stand_alone)) {
                if (tex_list_has_glyph(next)) {
                    /* maybe test direction, maybe also pass pre/post/replace as 4th argument*/
                    next = tex_handle_glyphrun(next, discretionary_group, text_direction_par);
                    next = tex_flatten_discretionaries(next, NULL, 1);
                }
            }
        }

        switch (saved_discretionary_current_component()) {
            case 0:
                if (next && length > 0) {
                    tex_set_disc_field(discnode, pre_break_code, next);
                }
                break;
            case 1:
                if (next && length > 0) {
                    tex_set_disc_field(discnode, post_break_code, next);
                }
                break;
            case 2:
                /*tex
                    Attach list |p| to the current list, and record its length; then finish up and
                    |return|.
                */
                if (next && length > 0) {
                    if (cur_mode == mmode && ! hyphenation_permitted(hyphenation_mode_par, permit_math_replace_hyphenation_mode)) {
                        tex_handle_error(
                            normal_error_type,
                            "Illegal math \\discretionary",
                            "Sorry: The third part of a discretionary break must be empty, in math formulas. I\n"
                            "had to delete your third part."
                        );
                        tex_flush_node_list(next);
                    } else {
                        tex_set_disc_field(discnode, no_break_code, next);
                    }
                }
                if (! hyphenation_permitted(hyphenation_mode_par, normal_hyphenation_mode)) {
                    halfword replace = disc_no_break_head(discnode);
                    cur_list.tail = node_prev(cur_list.tail);
                    node_next(cur_list.tail) = null;
                    if (replace) {
                        tex_tail_append(replace);
                        cur_list.tail = disc_no_break_tail(discnode);
                        tex_set_disc_field(discnode, no_break_code, null);
                        tex_set_discpart(discnode, replace, disc_no_break_tail(discnode), glyph_discpart_replace);
                    }
                    tex_flush_node(discnode);
                } else if (cur_mode == mmode && disc_class(discnode) != unset_disc_class) {
                    halfword noad = null;
                    cur_list.tail = node_prev(discnode);
                    node_prev(discnode ) = null;
                    node_next(discnode ) = null;
                    noad = tex_math_make_disc(discnode);
                    tex_tail_append(noad);
                }
                /*tex There are no other cases. */
                lmt_save_state.save_stack_data.ptr -= saved_discretionary_n_of_records;
                return;
            default:
                tex_confusion("finish discretionary");
                return;
        }
        saved_discretionary_update_component();
        tex_new_save_level(discretionary_group);
        tex_scan_left_brace();
        tex_push_nest();
        cur_list.mode = restricted_hmode;
        cur_list.space_factor = default_space_factor;
        cur_list.space_penalty = 0;
    }
}
