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

# include "luametatex.h"

/*tex These are for |show_activities|: */

# define page_goal lmt_page_builder_state.goal

/*tex

    \TEX\ is typically in the midst of building many lists at once. For example, when a math formula
    is being processed, \TEX\ is in math mode and working on an mlist; this formula has temporarily
    interrupted \TEX\ from being in horizontal mode and building the hlist of a paragraph; and this
    paragraph has temporarily interrupted \TEX\ from being in vertical mode and building the vlist
    for the next page of a document. Similarly, when a |\vbox| occurs inside of an |\hbox|, \TEX\ is
    temporarily interrupted from working in restricted horizontal mode, and it enters internal
    vertical mode. The \quote {semantic nest} is a stack that keeps track of what lists and modes
    are currently suspended.

    At each level of processing we are in one of six modes:

    \startitemize[n]
        \startitem
            |vmode| stands for vertical mode (the page builder);
        \stopitem
        \startitem
            |hmode| stands for horizontal mode (the paragraph builder);
        \stopitem
        \startitem
            |mmode| stands for displayed formula mode;
        \stopitem
        \startitem
            |-vmode| stands for internal vertical mode (e.g., in a |\vbox|);
        \stopitem
        \startitem
            |-hmode| stands for restricted horizontal mode (e.g., in an |\hbox|);
        \stopitem
        \startitem
            |-mmode| stands for math formula mode (not displayed).
        \stopitem
    \stopitemize

    The mode is temporarily set to zero while processing |\write| texts in the |ship_out| routine.

    Numeric values are assigned to |vmode|, |hmode|, and |mmode| so that \TEX's \quote {big semantic
    switch} can select the appropriate thing to do by computing the value |abs(mode) + cur_cmd|,
    where |mode| is the current mode and |cur_cmd| is the current command code.

    Per end December 2022 we no longer use the large mode numbers that also encode the command at 
    hand. That code is in the archive. 

*/

const char *tex_string_mode(int m)
{
    switch (m) {
        case nomode          : return "no mode";
        case vmode           : return "vertical mode";
        case hmode           : return "horizontal mode";
        case mmode           : return "display math mode";
        case internal_vmode  : return "internal vertical mode";
        case restricted_hmode: return "restricted horizontal mode";
        case inline_mmode    : return "inline math mode";
        default              : return "unknown mode";
    }
}

/*tex

    The state of affairs at any semantic level can be represented by five values:

    \startitemize
        \startitem
            |mode| is the number representing the semantic mode, as just explained.
        \stopitem
        \startitem
            |head| is a |pointer| to a list head for the list being built; |link(head)| therefore
            points to the first element of the list, or to |null| if the list is empty.
        \stopitem
        \startitem
            |tail| is a |pointer| to the final node of the list being built; thus, |tail=head| if
            and only if the list is empty.
        \stopitem
        \startitem
            |prev_graf| is the number of lines of the current paragraph that have already been put
            into the present vertical list.
        \stopitem
        \startitem
            |aux| is an auxiliary |memoryword| that gives further information that is needed to
            characterize the situation.
        \stopitem
    \stopitemize

    In vertical mode, |aux| is also known as |prev_depth|; it is the scaled value representing the
    depth of the previous box, for use in baseline calculations, or it is |<= -1000pt| if the next
    box on the vertical list is to be exempt from baseline calculations. In horizontal mode, |aux|
    is also known as |space_factor|; it holds the current space factor used in spacing calculations.
    In math mode, |aux| is also known as |incompleat_noad|; if not |null|, it points to a record
    that represents the numerator of a generalized fraction for which the denominator is currently
    being formed in the current list.

    There is also a sixth quantity, |mode_line|, which correlates the semantic nest with the
    user's input; |mode_line| contains the source line number at which the current level of nesting
    was entered. The negative of this line number is the |mode_line| at the level of the user's
    output routine.

    A seventh quantity, |eTeX_aux|, is used by the extended features eTeX. In math mode it is known
    as |delim_ptr| and points to the most recent |fence_noad| of a |math_left_group|.

    In horizontal mode, the |prev_graf| field is used for initial language data.

    The semantic nest is an array called |nest| that holds the |mode|, |head|, |tail|, |prev_graf|,
    |aux|, and |mode_line| values for all semantic levels below the currently active one.
    Information about the currently active level is kept in the global quantities |mode|, |head|,
    |tail|, |prev_graf|, |aux|, and |mode_line|, which live in a struct that is ready to be pushed
    onto |nest| if necessary.

    The math field is used by various bits and pieces in |texmath.w|

    This implementation of \TEX\ uses two different conventions for representing sequential stacks.

    \startitemize[n]

        \startitem
            If there is frequent access to the top entry, and if the stack is essentially never
            empty, then the top entry is kept in a global variable (even better would be a machine
            register), and the other entries appear in the array |stack[0 .. (ptr-1)]|. The semantic
            stack is handled this way.
        \stopitem

        \startitem
            If there is infrequent top access, the entire stack contents are in the array |stack[0
            .. (ptr - 1)]|. For example, the |save_stack| is treated this way, as we have seen.
        \stopitem

    \stopitemize

    In |nest_ptr| we have the first unused location of |nest|, and |max_nest_stack| has the maximum
    of |nest_ptr| when pushing. In |shown_mode| we store the most recent mode shown by
    |\tracingcommands| and with |save_tail| we can examine whether we have an auto kern before a
    glue.

*/

nest_state_info lmt_nest_state = {
    .nest       = NULL,
    .nest_data  = {
        .minimum   = min_nest_size,
        .maximum   = max_nest_size,
        .size      = siz_nest_size,
        .step      = stp_nest_size,
        .allocated = 0,
        .itemsize  = sizeof(list_state_record),
        .top       = 0,
        .ptr       = 0,
        .initial   = memory_data_unset,
        .offset    = 0,
        .extra     = 0, 
    },
    .shown_mode = 0,
    .math_mode  = 0,
};

/*tex

    We will see later that the vertical list at the bottom semantic level is split into two parts;
    the \quote {current page} runs from |page_head| to |page_tail|, and the \quote {contribution
    list} runs from |contribute_head| to |tail| of semantic level zero. The idea is that contributions
    are first formed in vertical mode, then \quote {contributed} to the current page (during which
    time the page|-|breaking decisions are made). For now, we don't need to know any more details
    about the page-building process.

*/

# define reserved_nest_slots 0

void tex_initialize_nest_state(void)
{
    int size = lmt_nest_state.nest_data.minimum;
    lmt_nest_state.nest = aux_allocate_clear_array(sizeof(list_state_record), size, reserved_nest_slots);
    if lmt_likely(lmt_nest_state.nest) {
        lmt_nest_state.nest_data.allocated = size;
    } else {
        tex_overflow_error("nest", size);
    }
}

static int tex_aux_room_on_nest_stack(void) /* quite similar to save_stack checker so maybe share */
{
    int top = lmt_nest_state.nest_data.ptr;
    if (top > lmt_nest_state.nest_data.top) {
        lmt_nest_state.nest_data.top = top;
        if lmt_unlikely(top > lmt_nest_state.nest_data.allocated) {
            list_state_record *tmp = NULL;
            top = lmt_nest_state.nest_data.allocated + lmt_nest_state.nest_data.step;
            if (top > lmt_nest_state.nest_data.size) {
                top = lmt_nest_state.nest_data.size;
            }
            if (top > lmt_nest_state.nest_data.allocated) {
                lmt_nest_state.nest_data.allocated = top;
                tmp = aux_reallocate_array(lmt_nest_state.nest, sizeof(list_state_record), top, reserved_nest_slots);
                lmt_nest_state.nest = tmp;
            }
            lmt_run_memory_callback("nest", tmp ? 1 : 0);
            if (! tmp) {
                tex_overflow_error("nest", top);
                return 0;
            }
        }
    }
    return 1;
}

/*tex 
    We start out with the page, that is, the main vertical list. 
*/

static inline void tex_aux_nesting_reset_state(void)
{
 // cur_list.last_state.line_width        = 0;
 // cur_list.last_state.line_count        = 0;
 // cur_list.last_state.hang_indent       = 0;
 // cur_list.last_state.hang_left_indent  = 0;
 // cur_list.last_state.hang_right_indent = 0;
 // cur_list.last_state.hang_slack        = 0;
 // cur_list.last_state.hang_left_slack   = 0;
 // cur_list.last_state.hang_right_slack  = 0;
    memset(&cur_list.last_state, 0, sizeof(cur_list.last_state));
}

void tex_initialize_nesting(void)
{
    /*tex |ignore_depth_criterion_par| is not yet available! */
    lmt_nest_state.nest_data.ptr = 0;
    lmt_nest_state.nest_data.top = 0;
    lmt_nest_state.shown_mode    = 0;
    lmt_nest_state.math_mode     = 0;
# if 1
    cur_list.mode               = vmode;
    cur_list.head               = contribute_head;
    cur_list.tail               = contribute_head;
    cur_list.delimiter          = null;
    cur_list.prev_graf          = 0;
    cur_list.mode_line          = 0;
    cur_list.prev_depth         = ignore_depth;
    cur_list.space_factor       = default_space_factor;
    cur_list.space_penalty      = 0;
    cur_list.incomplete_noad    = null;
    cur_list.direction_stack    = null;
    cur_list.math_dir           = 0;
    cur_list.math_style         = -1;
    cur_list.math_main_style    = -1;
    cur_list.math_parent_style  = -1;
    cur_list.math_flatten       = 1;
    cur_list.math_begin         = unset_noad_class;
    cur_list.math_end           = unset_noad_class;
    cur_list.math_mode          = 0;
    cur_list.math_scale         = 0;
    cur_list.options            = 0;
# else 
    cur_list = (list_state_record) {
        .mode              = vmode,
        .head              = contribute_head,
        .tail              = contribute_head,
        .delimiter         = null,
        .prev_graf         = 0,
        .mode_line         = 0,
        .prev_depth        = ignore_depth,
        .space_factor      = default_space_factor,
        .space_penalty     = 0,
        .incomplete_noad   = null,
        .direction_stack   = null,
        .math_dir          = 0,
        .math_style        = -1,
        .math_main_style   = -1,
        .math_parent_style = -1,
        .math_flatten      = 1,
        .math_begin        = unset_noad_class,
        .math_end          = unset_noad_class,
        .math_mode         = 0,
        .math_scale        = 0;
        .options           = 0,
    };
# endif 
    tex_aux_nesting_reset_state();
}

halfword tex_pop_tail(void)
{
    if (cur_list.tail != cur_list.head) {
        halfword r = cur_list.tail;
        halfword n = node_prev(r);
        if (node_next(n) != r) {
            n = cur_list.head;
            while (node_next(n) != r) {
                n = node_next(n);
            }
        }
        cur_list.tail = n;
        node_prev(r) = null;
        node_next(n) = null;
        return r;
    } else {
        return null;
    }
}

/*tex

    When \TEX's work on one level is interrupted, the state is saved by calling |push_nest|. This
    routine changes |head| and |tail| so that a new (empty) list is begun; it does not change
    |mode| or |aux|.

*/

void tex_push_nest(void)
{
    int previous = lmt_nest_state.nest_data.ptr;
    lmt_nest_state.nest_data.ptr += 1;
 // lmt_nest_state.shown_mode = 0; // needs checking 
    lmt_nest_state.math_mode = 0;
    if lmt_likely(tex_aux_room_on_nest_stack()) {
        list_state_record *top = &lmt_nest_state.nest[previous];
# if 1
        cur_list.mode              = top->mode;
        cur_list.head              = tex_new_temp_node();
        cur_list.tail              = cur_list.head;
        cur_list.delimiter         = null;
        cur_list.prev_graf         = 0;
        cur_list.mode_line         = lmt_input_state.input_line;
        cur_list.prev_depth        = top->prev_depth;
        cur_list.space_factor      = top->space_factor;
        cur_list.space_penalty     = top->space_penalty;
        cur_list.incomplete_noad   = top->incomplete_noad;
        cur_list.direction_stack   = null;
        cur_list.math_dir          = 0;
        cur_list.math_style        = -1;
        cur_list.math_main_style   = top->math_main_style;
        cur_list.math_parent_style = top->math_parent_style;
        cur_list.math_flatten      = 1;
        cur_list.math_begin        = unset_noad_class;
        cur_list.math_end          = unset_noad_class;
     // cur_list.math_begin        = top->math_begin;
     // cur_list.math_end          = top->math_end;
        cur_list.math_scale        = 0;
        cur_list.math_mode         = 0;
        cur_list.options           = 0;
# else
        cur_list = (list_state_record) {
            .mode              = top->mode,
            .head              = null,
            .tail              = null,
            .delimiter         = null,
            .prev_graf         = 0,
            .mode_line         = lmt_input_state.input_line,
            .prev_depth        = top->prev_depth,
            .space_factor      = top->space_factor,
            .space_penalty     = top->space_penalty,
            .incomplete_noad   = top->incomplete_noad,
            .direction_stack   = null,
            .math_dir          = 0,
            .math_style        = -1,
            .math_main_style   = top->math_main_style,
            .math_parent_style = top->math_parent_style,
            .math_flatten      = 1,
            .math_begin        = unset_noad_class,
            .math_end          = unset_noad_class,
         // .math_begin        = top->math_begin,
         // .math_end          = top->math_end,
            .math_scale        = 0,
            .math_mode         = 0,
            .options           = 0,
        };
        cur_list.head = tex_new_temp_node(),
        cur_list.tail = cur_list.head;
# endif
        tex_aux_nesting_reset_state();
    } else {
        tex_overflow_error("semantic nest size", lmt_nest_state.nest_data.size);
    }
}

/*tex

    Conversely, when \TEX\ is finished on the current level, the former state is restored by
    calling |pop_nest|. This routine will never be called at the lowest semantic level, nor will
    it be called unless |head| is a node that should be returned to free memory.

*/

void tex_pop_nest(void)
{
    if (cur_list.head) {
        /* tex_free_node(cur_list.head, temp_node_size); */ /* looks fragile */
        tex_flush_node(cur_list.head);
        /*tex Just to be sure, in case we access from \LUA: */
     // cur_list.head = null;
     // cur_list.tail = null;
    }
    --lmt_nest_state.nest_data.ptr;
}

/*tex Here is a procedure that displays what \TEX\ is working on, at all levels. */

void tex_show_activities(void)
{
    tex_print_nlp();
    for (int p = lmt_nest_state.nest_data.ptr; p >= 0; p--) {
        list_state_record n = lmt_nest_state.nest[p];
        tex_print_format("%l[%M entered at line %i%s]", n.mode, abs(n.mode_line), n.mode_line < 0 ? " (output routine)" : ""); // %L
        if (p == 0) {
            /*tex Show the status of the current page */
            if (page_head != lmt_page_builder_state.tail) {
                tex_print_format("%l[current page:%s]", lmt_page_builder_state.output_active ? " (held over for next output)" : "");
                tex_show_box(node_next(page_head));
                if (lmt_page_builder_state.contents != contribute_nothing) {
                    halfword r;
                    tex_print_format("%l[total height %P, goal height %p]",
                        page_total, page_stretch, page_fistretch, page_filstretch, page_fillstretch, page_filllstretch, page_shrink,
                        page_goal
                    );
                    r = node_next(page_insert_head);
                    while (r != page_insert_head) {
                        halfword index = insert_index(r);
                        halfword multiplier = tex_get_insert_multiplier(index);
                        halfword size = multiplier == scaling_factor ? insert_total_height(r) : tex_x_over_n_factor(insert_total_height(r)) * multiplier;
                     // halfword size = multiplier == scaling_factor ? insert_total_height(r) : insert_total_height(r) * multiplier / scaling_factor;
                     // halfword size = multiplier == scaling_factor ? insert_total_height(r) : scaledround(insert_total_height(r) * multiplier / scaling_factor_double) ;
                        if (node_type(r) == split_node && node_subtype(r) == insert_split_subtype) {
                            halfword q = page_head;
                            halfword n = 0;
                            do {
                                q = node_next(q);
                                if (node_type(q) == insert_node && split_insert_index(q) == insert_index(r)) {
                                    ++n;
                                }
                            } while (q != split_broken_insert(r));
                            tex_print_format("%l[insert %i adds %p, might split to %i]", index, size, n);
                        } else {
                            tex_print_format("%l[insert %i adds %p]", index, size);
                        }
                        r = node_next(r);
                    }
                }
            }
            if (node_next(contribute_head)) {
                tex_print_format("%l[recent contributions:]");
            }
        }
        tex_print_format("%l[begin list]");
        tex_show_box(node_next(n.head));
        tex_print_format("%l[end list]");
        /*tex Show the auxiliary field, |a|. */
        switch (n.mode) {
            case vmode:
            case internal_vmode:
                {
                    if (n.prev_depth <= ignore_depth_criterion_par) {
                        tex_print_format("%l[prevdepth ignored");
                    } else {
                        tex_print_format("%l[prevdepth %p", n.prev_depth);
                    }
                    if (n.prev_graf != 0) {
                        tex_print_format(", prevgraf %i line%s", n.prev_graf, n.prev_graf == 1 ? "" : "s");
                    }
                    tex_print_char(']');
                    break;
                }
            case mmode:
            case inline_mmode:
                {
                    if (n.incomplete_noad) {
                        tex_print_format("%l[this will be denominator of:]");
                        tex_print_format("%l[begin list]");
                        tex_show_box(n.incomplete_noad);
                        tex_print_format("%l[end list]");
                    }
                    break;
                }
        }
    }
}

int tex_vmode_nest_index(void)
{
    int p = lmt_nest_state.nest_data.ptr; /* index into |nest| */
    while (! is_v_mode(lmt_nest_state.nest[p].mode)) {
        --p;
    }
    return p;
}

void tex_tail_prepend(halfword n) 
{
    if (n) {
        if (cur_list.head == cur_list.tail) {
            tex_tail_append(n);
        } else {
            tex_couple_nodes(node_prev(cur_list.tail), n);
            tex_couple_nodes(n, cur_list.tail);
        }
    }
}

void tex_tail_append(halfword p)
{
    if (p) {
        node_next(cur_list.tail) = p;
        node_prev(p) = cur_list.tail;
        cur_list.tail = p;
    }
}

/*tex

    Splitting and some redundancy is cleaner than combining with several target checks, also
    because we don't know what we'll add in the future. We migh talso add some more specific
    tracing.

    We assume that the callback manages the nodes and cleans up if needed. This feature is
    experimental and will be so for a while. For sure there are issues.

*/

# if (delayed_glue_supported == 1)

static void tex_aux_tail_cleanup(halfword current)
{
    while (current) {
        if (node_type(current) == glue_node && (glue_options(current) & glue_option_delay)) {
            tex_remove_glue_option(current, glue_option_delay);
        }
        current = node_next(current);
    }
}

void tex_delayed_glue_check(int target, int location) /* TODO: often empty */
{
    if (0) {
        halfword head, tail;
        if (target == delayed_glue_target_unknown) {
         // target = lmt_nest_state.nest_data.ptr == 1 ? delayed_glue_target_mvl : delayed_glue_target_current;
            target = lmt_nest_state.nest_data.ptr == 0 ? delayed_glue_target_mvl : delayed_glue_target_current;
        }
        switch (target) {
            case delayed_glue_target_current:
                head = cur_list.head;
                tail = cur_list.tail;
                if (head == tail) {
                    return;
                } else {
                    break;
                }
            case delayed_glue_target_mvl:
                head = contribute_head;
                tail = contribute_tail;
                if (head == tail) {
                    return;
                } else {
                    break;
                }
            default:
                return;
        }
        {
            int callback_id = lmt_callback_defined(delayed_glue_callback);
            if (callback_id > 0) {
                halfword result = null;
               switch (target) {
                   case delayed_glue_target_current:
                       head = node_next(head);
                       node_next(cur_list.head) = null;
                       cur_list.tail = cur_list.head;
                       break;
                   case delayed_glue_target_mvl:
                       head = node_next(head);
                       node_next(contribute_head) = null;
                       contribute_tail = contribute_head;
                       break;
               }
               node_prev(head) = null;
               lmt_run_callback(lmt_lua_state.lua_instance, callback_id, "ddNN->N", target, location, head, tail, &result);
               if (result) {
                   tex_aux_tail_cleanup(result);
                   tail = tex_tail_of_node_list(result);
                   switch (target) {
                       case delayed_glue_target_current:
                           tex_couple_nodes(cur_list.head, result);
                            cur_list.tail = tail;
                            break;
                        case delayed_glue_target_mvl:
                            tex_couple_nodes(contribute_head, result);
                            contribute_tail = tail;
                            break;
                    }
                }
            }
        }
    }
}

int tex_delayed_glue_par_skipped(void)
{
    halfword tail;
    if (lmt_nest_state.nest_data.ptr > 1) {
        tail = cur_list.tail;
    } else if (node_type(contribute_tail) != temp_node) {
        tail = contribute_tail;
    } else if (node_type(page_tail) != temp_node) {
        tail = page_tail;
    } else {
        return 0;
    }
    return node_type(tail) == glue_node && (glue_options(tail) & glue_option_has_parskip);
}

# endif

/*tex

    In the end this is nicer than the ugly look back and set extensible properties on a last node, 
    although that is a bit more generic. So we're back at an old \MKIV\ feature that looks ahead 
    but this time selective and therefore currently only for a few math node types. Math has a 
    synchronization issue: we can do the same with a node list handler but then we need to plug 
    into |mlist_to_hlist| which is nto pretty either.  Eventually I might do that anyway and then 
    this will disappear. This more \quote {immediate} approach also has the benefit that we can 
    cleanup immediately. (It could be used a bit like runtime captures in \LUA.)

*/

halfword tex_tail_fetch_callback(void)
{
    halfword tail = cur_list.tail;
    if (node_type(tail) == boundary_node && node_subtype(tail) == lua_boundary) { 
        cur_list.tail = node_prev(tail);
        node_next(cur_list.tail) = null;
        node_prev(tail) = null;
        node_next(tail) = null;
        return tail;
    } else {
        return null;
    }
}

halfword tex_tail_apply_callback(halfword p, halfword c)
{
    if (p && c) { 
        int callback_id = lmt_callback_defined(tail_append_callback);
        if (callback_id > 0) {
            lmt_run_callback(lmt_lua_state.lua_instance, callback_id, "Ndd->N", p, boundary_data(c), boundary_reserved(c), &p);
        }
        tex_flush_node(c);
    }
    return p;
}

void tex_tail_append_list(halfword p)
{
    if (p) { 
        node_next(cur_list.tail) = p;
        node_prev(p) = cur_list.tail;
        cur_list.tail = tex_tail_of_node_list(p);
    }
}

halfword tex_tail_append_callback(halfword p)
{
    halfword c = tex_tail_fetch_callback();
    if (c) { 
        p = tex_tail_apply_callback(p, c);
    }
    tex_tail_append_list(p);
    return p;
}

/*tex 
    This is an experiment. We reserve slot 0 for special purposes. 
*/


/* 
    todo: stack so that we can nest 
    todo: handle prev_depth 
    todo: less fields needed, so actually we can have a 'register' or maybe even use a box 
    todo: check if at the outer level 
*/

/* 
    contribute_head : nest[0].head : temp node 
    contribute_tail : nest[0].tail 
*/

mvl_state_info lmt_mvl_state = {
    .mvl       = NULL,
    .mvl_data  = {
        .minimum   = min_mvl_size,
        .maximum   = max_mvl_size,
        .size      = memory_data_unset,
        .step      = stp_mvl_size,
        .allocated = 0,
        .itemsize  = sizeof(list_state_record),
        .top       = 0,
        .ptr       = 0,
        .initial   = memory_data_unset,
        .offset    = 0,
        .extra     = 0, 
    },
};

static void tex_aux_reset_mvl(int i) 
{
    lmt_mvl_state.mvl[i] = (list_state_record) {
        .mode              = vmode,
        .head              = null,
        .tail              = null,
        .delimiter         = null,
        .prev_graf         = 0,
        .mode_line         = 0,
        .prev_depth        = ignore_depth,   
        .space_factor      = default_space_factor,
        .space_penalty     = 0,
        .incomplete_noad   = null,
        .direction_stack   = null,
        .math_dir          = 0,
        .math_style        = -1,
        .math_main_style   = -1,
        .math_parent_style = -1,
        .math_flatten      = 1,
        .math_begin        = unset_noad_class,
        .math_end          = unset_noad_class,
        .options           = 0,
    };
}

# define reserved_mvl_slots 0

void tex_initialize_mvl_state(void)
{
    list_state_record *tmp = aux_allocate_clear_array(sizeof(list_state_record), lmt_mvl_state.mvl_data.minimum, 1);
    if lmt_likely(tmp) {
        lmt_mvl_state.mvl = tmp;
        lmt_mvl_state.mvl_data.allocated = lmt_mvl_state.mvl_data.minimum;
        lmt_mvl_state.mvl_data.top = lmt_mvl_state.mvl_data.minimum;
        lmt_mvl_state.mvl_data.ptr = 0;
    } else {
        tex_overflow_error("mvl", lmt_mvl_state.mvl_data.minimum);
    }
    tex_aux_reset_mvl(0);
    lmt_mvl_state.slot = 0;
}

static int tex_valid_mvl_id(halfword n)
{ 
 // if (lmt_nest_state.nest_data.ptr > 0) {
 //     tex_handle_error(
 //         normal_error_type,
 //         "An mlv command can only be used at the outer level.",
 //         "You cannot use an mlv command inside a box."
 //     );
 // } else 
    if (n < min_mvl_index || n > max_mvl_index) {
        return 0;
    } else if (n <= lmt_mvl_state.mvl_data.ptr) {
        return 1;
    } else if (n <= lmt_mvl_state.mvl_data.top) {
        lmt_mvl_state.mvl_data.ptr = n;
        return 1;
    } else if (n <= lmt_mvl_state.mvl_data.maximum && lmt_mvl_state.mvl_data.top < lmt_mvl_state.mvl_data.maximum) {
        list_state_record *tmp = NULL;
        int top = n + lmt_mvl_state.mvl_data.step;
        if (top > lmt_mvl_state.mvl_data.maximum) {
            top = lmt_mvl_state.mvl_data.maximum;
        }
        tmp = aux_reallocate_array(lmt_mvl_state.mvl, sizeof(list_state_record), top, 1); // 1 slack reserved_mvl_slots
        if (tmp) {
            size_t extra = ((size_t) top - lmt_mvl_state.mvl_data.top) * sizeof(list_state_record);
            memset(&tmp[lmt_mvl_state.mvl_data.top + 1], 0, extra);
            lmt_mvl_state.mvl = tmp;
            lmt_mvl_state.mvl_data.allocated = top;
            lmt_mvl_state.mvl_data.top = top;
            lmt_mvl_state.mvl_data.ptr = n;
            return 1;
        }
    }
    tex_overflow_error("mvl", lmt_mvl_state.mvl_data.maximum);
    return 0;
}

void tex_start_mvl(void)
{
    halfword index = 0; 
    halfword options = 0;
    halfword prevdepth = max_dimen;
    while (1) {
        switch (tex_scan_character("iop", 0, 1, 0)) {
            case 'i':
                if (tex_scan_mandate_keyword("index", 1)) {
                    index = tex_scan_integer(0, NULL, NULL);
                }
                break;
            case 'o':
                if (tex_scan_mandate_keyword("options", 1)) {
                    options = tex_scan_integer(0, NULL, NULL);
                }
                break;
            case 'p':
                if (tex_scan_mandate_keyword("prevdepth", 1)) {
                    prevdepth = tex_scan_dimension(0, 0, 0, 0, NULL, NULL);
                }
                break;
            default:
                goto DONE;
        }
    }
  DONE:
    if (! index) { 
        index = tex_scan_integer(0, NULL, NULL);
    }
    if (lmt_mvl_state.slot) {
        /*tex We're already collecting. */
    } else if (index <= 0) {
        /*tex We have an invalid id. */
    } else if (! tex_valid_mvl_id(index)) {
        /*tex We're in trouble. */
    } else { 
        /*tex We're can start collecting. */
        list_state_record *mvl = &lmt_mvl_state.mvl[index];
        int start = ! mvl->head;
        if (options & mvl_ignore_prev_depth) { 
            prevdepth = ignore_depth_criterion_par;
        } else if (options & mvl_no_prev_depth) { 
            prevdepth = 0;
        } else if (prevdepth == max_dimen) { 
            prevdepth = lmt_mvl_state.mvl[index].prev_depth;
        }
        if (tracing_mvl_par) { 
            tex_begin_diagnostic();
            tex_print_format("%l[mvl: index %i, options %x, prevdepth %p, %s]", index, options, prevdepth, start ? "start" : "restart");
            tex_end_diagnostic();
        }
        if (start) { 
            mvl->head = tex_new_temp_node();
            mvl->tail = mvl->head;
        }
        mvl->options = options;
        lmt_mvl_state.mvl[0].prev_depth = lmt_nest_state.nest[0].prev_depth;
        lmt_nest_state.nest[0].prev_depth = prevdepth;
        lmt_mvl_state.slot = index;
    }
}

void tex_stop_mvl(void)
{
    halfword index = lmt_mvl_state.slot;
    if (index) {
        list_state_record *mvl = &lmt_mvl_state.mvl[index];
        int something = mvl->tail != mvl->head;
        if (tracing_mvl_par) { 
            tex_begin_diagnostic();
            tex_print_format("%l[mvl: index %i, options %x, stop with%s contributions]", index, mvl->options, something ? "" : "out");
            tex_end_diagnostic();
        }
        if (something && (mvl->options & mvl_discard_bottom)) {
            halfword last = mvl->tail;
            while (last) {
                if (non_discardable(last)) {
                    break;
                } else if (node_type(last) == kern_node && ! (node_subtype(last) == explicit_kern_subtype)) {
                    break;
                } else { 
                    halfword preceding = node_prev(last);
                    node_next(preceding) = null;
                    tex_flush_node(last);
                    mvl->tail = preceding;
                    if (mvl->head == preceding) { 
                        break;
                    } else {
                        last = preceding;
                    }
                }
            }
        }
        mvl->prev_depth = lmt_nest_state.nest[0].prev_depth;
        lmt_nest_state.nest[0].prev_depth = lmt_mvl_state.mvl[0].prev_depth;
        lmt_mvl_state.slot = 0;
    }
}

# define page_callback 1

halfword tex_flush_mvl(halfword index)
{
 // halfword index = tex_scan_integer(0, NULL);
    if (lmt_mvl_state.slot) { 
        /*tex We're collecting. */
        return null; 
    } else if (! tex_valid_mvl_id(index)) {
        /*tex We're in trouble. */
        return null;
    } else if (! lmt_mvl_state.mvl[index].tail || lmt_mvl_state.mvl[index].tail == lmt_mvl_state.mvl[index].head) {
        /*tex We collected nothing or are invalid. */
        return null;
    } else { 
        /*tex We collected something. */
        halfword head = node_next(lmt_mvl_state.mvl[index].head);
        tex_flush_node(lmt_mvl_state.mvl[index].head);
        tex_aux_reset_mvl(index);
        if (tracing_mvl_par) { 
            tex_begin_diagnostic();
            tex_print_format("%l[mvl: index %i, %s]", index, "flush");
            tex_end_diagnostic();
        }
        node_prev(head) = null;
# if page_callback
        return tex_vpack(head, 0, packing_additional, max_dimension, 0, holding_none_option, NULL);
# else 
        if (head) {
            return tex_filtered_vpack(head, 0, packing_additional, max_dimension, 0, 0, 0, node_attr(head), 0, 0, NULL);
        } else {
            return tex_vpack(head, 0, packing_additional, max_dimension, 0, holding_none_option, NULL);
        }
# endif
    }
}

halfword tex_preroll_mvl(halfword index)
{
 // halfword index = tex_scan_integer(0, NULL);
    if (lmt_mvl_state.slot) {
        /*tex We're collecting. */
        return null;
    } else if (! tex_valid_mvl_id(index)) {
        /*tex We're in trouble. */
        return null;
    } else if (! lmt_mvl_state.mvl[index].tail || lmt_mvl_state.mvl[index].tail == lmt_mvl_state.mvl[index].head) {
        /*tex We collected nothing or are invalid. */
        return null;
    } else {
        /*tex We collected something. */
        scaledwhd whd = tex_natural_vsizes(lmt_mvl_state.mvl[index].head, null, 0, 0, 0, 0);
        halfword box = tex_new_node(vlist_node, unknown_list);
        box_width(box) = whd.wd;
        box_height(box) = whd.ht;
        box_depth(box) = whd.dp;
        return box;
    }
}

int tex_appended_mvl(halfword context, halfword boundary)
{
    if (! lmt_mvl_state.slot) {
        /*tex We're not collecting. */
        return 0;
    } else { 
# if page_callback
        if (node_next(contribute_head) && ! lmt_page_builder_state.output_active) {
     // if (! lmt_page_builder_state.output_active) {
            lmt_buildpage_callback(context, boundary);
        } 
# endif 
        if (node_next(contribute_head) && ! lmt_page_builder_state.output_active) {
            halfword first = node_next(contribute_head); 
            int assign = lmt_mvl_state.mvl[lmt_mvl_state.slot].tail == lmt_mvl_state.mvl[lmt_mvl_state.slot].head;
            if (assign && (lmt_mvl_state.mvl[lmt_mvl_state.slot].options & mvl_discard_top)) {
                while (first) {
                    if (non_discardable(first)) {
                        break;
                    } else if (node_type(first) == kern_node && ! (node_subtype(first) == explicit_kern_subtype)) {
                        break;
                    } else { 
                        halfword following = node_next(first);
                        node_prev(following) = null;
                        tex_flush_node(first);
                        first = following;
                    }
                }
            }
            if (contribute_head != contribute_tail && first) {
                if (tracing_mvl_par) { 
                    tex_begin_diagnostic();
                    tex_print_format("%l[mvl: index %i, %s]", lmt_mvl_state.slot, assign ? "assign" : "append");
                    tex_end_diagnostic();
                }
                if (assign) { 
                    tex_couple_nodes(lmt_mvl_state.mvl[lmt_mvl_state.slot].head, first);
                } else { 
                    tex_couple_nodes(lmt_mvl_state.mvl[lmt_mvl_state.slot].tail, first);
                }
                lmt_mvl_state.mvl[lmt_mvl_state.slot].tail = contribute_tail;
            }
            node_next(contribute_head) = null;
            contribute_tail = contribute_head;
        }
        return 1;
    }
}

int tex_current_mvl(halfword *head, halfword *tail)
{
    if (lmt_mvl_state.slot == 0) { 
        if (head && tail) {
            *head = node_next(page_head);
            *tail = lmt_page_builder_state.tail;
        }
        return 0; 
    } else if (lmt_mvl_state.slot > 0) {
        if (head && tail) {
            *head = lmt_mvl_state.mvl[lmt_mvl_state.slot].head;
            *tail = lmt_mvl_state.mvl[lmt_mvl_state.slot].tail;
        }
        return lmt_mvl_state.slot; 
    } else { 
        if (head && tail) {
            *head = null;
            *tail = null;
        }
        return 0;
    } 
}
