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

/*tex

    Here we implement sparse arrays with an embedded save stack. These functions are called very
    often but a few days of experimenting proved that there is not much to gain (if at all) from
    using macros or optimizations like pre-allocating and fast access to the first 128 entries. In
    practice the overhead is mostly in accessing memory and not in (probably inlined) calls. So, we
    should accept fate and wait for faster memory. It's the price we pay for being unicode on the
    one hand and sparse on the other.

*/

/*  
    
    We could do this: 

        v // 4  v % 4  =>  v >> 2  v & 3

    but let's just assume that the compiler optimizes it. 

    We could also pack the catcodes in single bytes but then we need to work with 
    
        nn = odd(n) ? n + 1 : n; 

    and things like:

        >> 1 
        & 0xF

    and for the setter that is more work because we need to overwrite the existing byte that now has
    two values and we also have the stack to deal with. We would save some memory but not that much. 

*/

/* 
 
    We don't need to test if a tree is defined because we assume that this happens in the callers 
    which saves some overhead here. (See older code for commented tests.)

 */

# include "luametatex.h"

sparse_state_info lmt_sparse_state = {
    .sparse_data = {
        .minimum   = memory_data_unset,
        .maximum   = memory_data_unset,
        .size      = memory_data_unset,
        .step      = memory_data_unset,
        .allocated = 0,
        .itemsize  = 1,
        .top       = memory_data_unset,
        .ptr       = memory_data_unset,
        .initial   = memory_data_unset,
        .offset    = 0,
        .extra     = 0, 
}
};

void *sa_malloc_array(int recordsize, int size)
{
    int allocated = recordsize * size;
    lmt_sparse_state.sparse_data.allocated += allocated;
    return lmt_memory_malloc((size_t) allocated);
}

void *sa_realloc_array(void *p, int recordsize, int size, int step)
{
    int deallocated = recordsize * size;
    int allocated = recordsize * (size + step);
    lmt_sparse_state.sparse_data.allocated += (allocated - deallocated);
    return lmt_memory_realloc(p, (size_t) allocated);
}

void *sa_calloc_array(int recordsize, int size)
{
    int allocated = recordsize * size;
    lmt_sparse_state.sparse_data.allocated += allocated;
    return lmt_memory_calloc((size_t) size, recordsize);
}

void sa_wipe_array(void *head, int recordsize, int size)
{
    memset(head, 0, recordsize * ((size_t) size));
}

void *sa_free_array(void *p)
{
    lmt_memory_free(p);
    return NULL;
}

/*tex

    Once we have two variants allocated we can dump and undump a |LOWPART| array in one go. But
    not yet. Currently the waste of one extra dummy int is cheaper than multiple functions. We 
    anyway only save when needed (which saves 4 million stores on the mathincontext manual which 
    is actually not noticeable in performance at all). 

*/

static void sa_aux_store_stack(const sa_tree head, int n, const sa_tree_item v1, const sa_tree_item v2, int gl)
{
    sa_stack_item st = {
        .code    = n,
        .value_1 = v1,
        .value_2 = v2,
        .level   = gl
    };
    if (! head->stack) {
        head->stack = sa_malloc_array(sizeof(sa_stack_item), head->sa_stack_size);
    } else if (((head->sa_stack_ptr) + 1) >= head->sa_stack_size) {
        head->stack = sa_realloc_array(head->stack, sizeof(sa_stack_item), head->sa_stack_size, head->sa_stack_step);
        head->sa_stack_size += head->sa_stack_step;
     // printf("bumping %i stack to %i\n",head->identifier,head->sa_stack_size);
    }
    (head->sa_stack_ptr)++;
 // printf("store %i @ %i: code %i, v1 %i, v2 %i, level %i\n",head->identifier,head->sa_stack_ptr,st.code,st.value_1.int_value,st.value_2.int_value,st.level);
    head->stack[head->sa_stack_ptr] = st;
}

static void sa_aux_skip_in_stack(const sa_tree head, int n)
{
    if (head->stack) {
        int p = head->sa_stack_ptr;
        while (p > 0) {
            if (head->stack[p].code == n && head->stack[p].level > 0) {
                head->stack[p].level = -(head->stack[p].level);
            }
         // printf("skip %i: code %i, v1 %i, v2 %i, level %i\n",head->identifier,head->stack[p].code,head->stack[p].value_1.int_value,head->stack[p].value_2.int_value,head->stack[p].level);
            p--;
        }
    }
}

// # define LMT_SA_L_PART_1(n) (LMT_SA_L_PART(n)/4)
// # define LMT_SA_L_SLOT_1(n) (n%4)
// # define LMT_SA_LOWPART_1   (LMT_SA_LOWPART/4)
// 
// # define LMT_SA_L_PART_2(n) (LMT_SA_L_PART(n)/2)
// # define LMT_SA_L_SLOT_2(n) (n%2)
// # define LMT_SA_LOWPART_2   (LMT_SA_LOWPART/2)
// 
// # define LMT_SA_L_PART_4(n) (LMT_SA_L_PART(n))
// # define LMT_SA_L_SLOT_4(n) (n)
// # define LMT_SA_LOWPART_4   (LMT_SA_LOWPART)

# if (! sa_inline_get) 

int sa_get_item_0(const sa_tree head, int n)
{
    int h = LMT_SA_H_PART(n);
    if (head->tree[h]) {
        int m = LMT_SA_M_PART(n);
        if (head->tree[h][m]) {
            return get_nibble(head->tree[h][m][LMT_SA_L_PART(n)/8].uint_value, n);
        }
    }
    return (int) get_nibble(head->dflt.uint_value,0);
}

int sa_get_item_1(const sa_tree head, int n)
{
    int h = LMT_SA_H_PART(n);
    if (head->tree[h]) {
        int m = LMT_SA_M_PART(n);
        if (head->tree[h][m]) {
            return head->tree[h][m][LMT_SA_L_PART(n)/4].uchar_value[n%4];
        }
    }
    return (int) head->dflt.uchar_value[0];
}

int sa_get_item_2(const sa_tree head, int n)
{
    int h = LMT_SA_H_PART(n);
    if (head->tree[h]) {
        int m = LMT_SA_M_PART(n);
        if (head->tree[h][m]) {
            return head->tree[h][m][LMT_SA_L_PART(n)/2].ushort_value[n%2];
        }
    }
    return (int) head->dflt.ushort_value[0];
}

int sa_get_item_4(const sa_tree head, int n, sa_tree_item *v)
{
    int h = LMT_SA_H_PART(n);
    if (head->tree[h]) {
        int m = LMT_SA_M_PART(n);
        if (head->tree[h][m]) {
            *v = head->tree[h][m][LMT_SA_L_PART(n)];
            return 1;
        }
    }
    *v = head->dflt;
    return 0;
}

int sa_get_item_8(const sa_tree head, int n, sa_tree_item *v1, sa_tree_item *v2)
{
    int h = LMT_SA_H_PART(n);
    if (head->tree[h]) {
        int m = LMT_SA_M_PART(n);
        if (head->tree[h][m]) {
            int l = 2*LMT_SA_L_PART(n);
            *v1 = head->tree[h][m][l];
            *v2 = head->tree[h][m][l+1];
            return 1;
        }
    }
    *v1 = head->dflt;
    *v2 = head->dflt;
    return 0;
}

# endif 

void sa_set_item_0(const sa_tree head, int n, int v, int gl)
{
    int h = LMT_SA_H_PART(n);
    int m = LMT_SA_M_PART(n);
    int l = LMT_SA_L_PART(n);
    if (! head->tree[h]) {
        head->tree[h] = (sa_tree_item **) sa_calloc_array(sizeof(sa_tree_item *), LMT_SA_MIDPART);
    }
    if (! head->tree[h][m]) {
        head->tree[h][m] = (sa_tree_item *) sa_malloc_array(sizeof(sa_tree_item), LMT_SA_LOWPART/8);
        for (int i = 0; i < LMT_SA_LOWPART/8; i++) {
            head->tree[h][m][i] = head->dflt;
        }
    }
    if (gl <= 1) {
        sa_aux_skip_in_stack(head, n);
    } else if (get_nibble(head->tree[h][m][l/8].uint_value,n) != (unsigned int) v) {
        sa_aux_store_stack(head, n, head->tree[h][m][l/8], (sa_tree_item) { 0 }, gl);
    } else { 
        /*tex There is no change so we don't need to save the old value. */
    }
    head->tree[h][m][l/8].uint_value = set_nibble(head->tree[h][m][l/8].uint_value, n, v);
}

void sa_set_item_1(const sa_tree head, int n, int v, int gl)
{
    int h = LMT_SA_H_PART(n);
    int m = LMT_SA_M_PART(n);
    int l = LMT_SA_L_PART(n);
    if (! head->tree[h]) {
        head->tree[h] = (sa_tree_item **) sa_calloc_array(sizeof(sa_tree_item *), LMT_SA_MIDPART);
    }
    if (! head->tree[h][m]) {
        head->tree[h][m] = (sa_tree_item *) sa_malloc_array(sizeof(sa_tree_item), LMT_SA_LOWPART/4);
        for (int i = 0; i < LMT_SA_LOWPART/4; i++) {
            head->tree[h][m][i] = head->dflt;
        }
    }
    if (gl <= 1) {
        sa_aux_skip_in_stack(head, n);
    } else if (head->tree[h][m][l/4].uchar_value[n%4] != v) {
        sa_aux_store_stack(head, n, head->tree[h][m][l/4], (sa_tree_item) { 0 }, gl);
    } else { 
        /*tex There is no change so we don't need to save the old value. */
    }
    head->tree[h][m][l/4].uchar_value[n%4] = (unsigned char) v;
}

void sa_set_item_2(const sa_tree head, int n, int v, int gl)
{
    int h = LMT_SA_H_PART(n);
    int m = LMT_SA_M_PART(n);
    int l = LMT_SA_L_PART(n);
    if (! head->tree[h]) {
        head->tree[h] = (sa_tree_item **) sa_calloc_array(sizeof(sa_tree_item *), LMT_SA_MIDPART);
    }
    if (! head->tree[h][m]) {
        head->tree[h][m] = (sa_tree_item *) sa_malloc_array(sizeof(sa_tree_item), LMT_SA_LOWPART/2);
        for (int i = 0; i < LMT_SA_LOWPART/2; i++) {
            head->tree[h][m][i] = head->dflt;
        }
    }
    if (gl <= 1) {
        sa_aux_skip_in_stack(head, n);
    } else if (head->tree[h][m][l/2].ushort_value[n%2] != v) {
        sa_aux_store_stack(head, n, head->tree[h][m][l/2], (sa_tree_item) { 0 }, gl);
    } else { 
        /*tex There is no change so we don't need to save the old value. */
    }
    head->tree[h][m][l/2].ushort_value[n%2] = (unsigned short) v;
}

void sa_set_item_4(const sa_tree head, int n, const sa_tree_item v, int gl)
{
    int h = LMT_SA_H_PART(n);
    int m = LMT_SA_M_PART(n);
    int l = LMT_SA_L_PART(n);
    if (! head->tree[h]) {
        head->tree[h] = (sa_tree_item **) sa_calloc_array(sizeof(sa_tree_item *), LMT_SA_MIDPART);
    }
    if (! head->tree[h][m]) {
        head->tree[h][m] = (sa_tree_item *) sa_malloc_array(sizeof(sa_tree_item), LMT_SA_LOWPART);
        for (int i = 0; i < LMT_SA_LOWPART; i++) {
            head->tree[h][m][i] = head->dflt;
        }
    }
    if (gl <= 1) {
        sa_aux_skip_in_stack(head, n);
    } else if (head->tree[h][m][l].uint_value != v.uint_value) {
        sa_aux_store_stack(head, n, head->tree[h][m][l], (sa_tree_item) { 0 }, gl);
    } else { 
        /*tex There is no change so we don't need to save the old value. */
    }
    head->tree[h][m][l] = v;
}

void sa_set_item_8(const sa_tree head, int n, const sa_tree_item v1, const sa_tree_item v2, int gl)
{
    int h = LMT_SA_H_PART(n);
    int m = LMT_SA_M_PART(n);
    int l = 2*LMT_SA_L_PART(n);
    if (! head->tree[h]) {
        head->tree[h] = (sa_tree_item **) sa_calloc_array(sizeof(sa_tree_item *), LMT_SA_MIDPART);
    }
    if (! head->tree[h][m]) {
        head->tree[h][m] = (sa_tree_item *) sa_malloc_array(sizeof(sa_tree_item), 2 * LMT_SA_LOWPART);
        for (int i = 0; i < 2 * LMT_SA_LOWPART; i++) {
            head->tree[h][m][i] = head->dflt;
        }
    }
    if (gl <= 1) {
        sa_aux_skip_in_stack(head, n);
    } else if (head->tree[h][m][l].uint_value != v1.uint_value || head->tree[h][m][l+1].uint_value != v2.uint_value) {
        sa_aux_store_stack(head, n, head->tree[h][m][l], head->tree[h][m][l+1], gl);
    } else { 
        /*tex There is no change so we don't need to save the old value. */
   }
    head->tree[h][m][l] = v1;
    head->tree[h][m][l+1] = v2;
}

void sa_set_item_n(const sa_tree head, int n, int v, int gl)
{
    int h = LMT_SA_H_PART(n);
    int m = LMT_SA_M_PART(n);
    int l = LMT_SA_L_PART(n);
    int d = head->bytes == 0 ? 8 : (head->bytes == 1 ? 4 : (head->bytes == 2 ? 2 : 1));
    if (! head->tree[h]) {
        head->tree[h] = (sa_tree_item **) sa_calloc_array(sizeof(sa_tree_item *), LMT_SA_MIDPART);
    }
    if (! head->tree[h][m]) {
        head->tree[h][m] = (sa_tree_item *) sa_malloc_array(sizeof(sa_tree_item), LMT_SA_LOWPART/d);
        for (int i = 0; i < LMT_SA_LOWPART/d; i++) {
            head->tree[h][m][i] = head->dflt;
        }
    }
    if (gl <= 1) {
        sa_aux_skip_in_stack(head, n);
    } else {
        sa_aux_store_stack(head, n, head->tree[h][m][l/d], (sa_tree_item) { 0 }, gl);
    }
    switch (head->bytes) {
        case 0:
            head->tree[h][m][l/8].uint_value = set_nibble(head->tree[h][m][l/8].uint_value,n,v);
            break;
        case 1:
            head->tree[h][m][l/4].uchar_value[n%4] = (unsigned char) (v < 0 ? 0 : (v > 0xFF ? 0xFF : v));
            break;
        case 2:
            head->tree[h][m][l/2].ushort_value[n%2] = (unsigned short) (v < 0 ? 0 : (v > 0xFFFF ? 0xFFFF : v));
            break;
        case 4:
            head->tree[h][m][l].int_value = v;
            break;
    }
}

int sa_get_item_n(const sa_tree head, int n)
{
    int h = LMT_SA_H_PART(n);
    if (head->tree[h]) {
        int m = LMT_SA_M_PART(n);
        if (head->tree[h][m]) {
            switch (head->bytes) {
                case 0 : return (int) get_nibble(head->tree[h][m][LMT_SA_L_PART(n)/8].uint_value,n);
                case 1 : return (int) head->tree[h][m][LMT_SA_L_PART(n)/4].uchar_value[n%4];
                case 2 : return (int) head->tree[h][m][LMT_SA_L_PART(n)/2].ushort_value[n%2];
                case 4 : return (int) head->tree[h][m][LMT_SA_L_PART(n)  ].int_value;
            }
        }
    }
    switch (head->bytes) {
        case 0 : return (int) get_nibble(head->dflt.uint_value,0);
        case 1 : return (int) head->dflt.uchar_value[0];
        case 2 : return (int) head->dflt.ushort_value[0];
        case 4 : return (int) head->dflt.int_value;
        default: return 0;
    }
}

void sa_clear_stack(const sa_tree a)
{
    if (a) {
        a->stack = sa_free_array(a->stack);
        a->sa_stack_ptr = 0;
        a->sa_stack_size = a->sa_stack_step;
    }
}

void sa_destroy_tree(sa_tree a)
{
    if (a) {
        for (int h = 0; h < LMT_SA_HIGHPART; h++) {
            if (a->tree[h]) {
                for (int m = 0; m < LMT_SA_MIDPART; m++) {
                    a->tree[h][m] = sa_free_array(a->tree[h][m]);
                }
                a->tree[h] = sa_free_array(a->tree[h]);
            }
        }
        a->stack = sa_free_array(a->stack);
        a = sa_free_array(a);
    }
}

sa_tree sa_copy_tree(const sa_tree b)
{
    sa_tree a = (sa_tree) sa_malloc_array(sizeof(sa_tree_head), 1);
    a->sa_stack_step = b->sa_stack_step;
    a->sa_stack_size = b->sa_stack_size;
    a->bytes = b->bytes;
    a->identifier = b->identifier;
    a->dflt = b->dflt;
    a->stack = NULL;
    a->sa_stack_ptr = 0;
    sa_wipe_array(a->tree, sizeof(sa_tree_item *), LMT_SA_HIGHPART);
    for (int h = 0; h < LMT_SA_HIGHPART; h++) {
        if (b->tree[h]) {
            int slide = LMT_SA_LOWPART;
            switch (b->bytes) {
                case 0: slide =   LMT_SA_LOWPART/8; break;
                case 1: slide =   LMT_SA_LOWPART/4; break;
                case 2: slide =   LMT_SA_LOWPART/2; break;
                case 4: slide =   LMT_SA_LOWPART  ; break;
                case 8: slide = 2*LMT_SA_LOWPART  ; break;
            }
            a->tree[h] = (sa_tree_item **) sa_calloc_array(sizeof(void *), LMT_SA_MIDPART);
            for (int m = 0; m < LMT_SA_MIDPART; m++) {
                if (b->tree[h][m]) {
                    a->tree[h][m] = sa_malloc_array(sizeof(sa_tree_item), slide);
                    memcpy(a->tree[h][m], b->tree[h][m], sizeof(sa_tree_item) * slide);
                }
            }
        }
    }
    return a;
}

/*tex

    The main reason to fill in the lowest entry branches here immediately is that most of the sparse
    arrays have a bias toward \ASCII\ values. Allocating those here immediately improves the chance
    of the structure |a->tree[0][0][x]| being close together in actual memory locations. We could
    save less for type 0 stacks.

*/

sa_tree sa_new_tree(int identifier, int stacksize, int stackstep, int bytes, const sa_tree_item dflt)
{
    sa_tree_head *a = (sa_tree_head *) lmt_memory_malloc(sizeof(sa_tree_head));
    a->dflt = dflt;
    a->stack = NULL;
    sa_wipe_array(a->tree, sizeof(sa_tree_item *), LMT_SA_HIGHPART);
    a->tree[0] = (sa_tree_item **) sa_calloc_array(sizeof(sa_tree_item *), LMT_SA_MIDPART);
    a->sa_stack_size = stacksize;
    a->sa_stack_step = stackstep;
    a->bytes = bytes;
    a->identifier = identifier;
    a->sa_stack_ptr = 0;
    return (sa_tree) a;
}

void sa_restore_stack(const sa_tree head, int gl)
{
    if (head->stack) {
        while (head->sa_stack_ptr > 0 && abs(head->stack[head->sa_stack_ptr].level) >= gl) {
            sa_stack_item st = head->stack[head->sa_stack_ptr];
            if (st.level > 0) {
                int code = st.code;
                switch (head->bytes) {
                    case 0:
                        {
                            head->tree[LMT_SA_H_PART(code)][LMT_SA_M_PART(code)][LMT_SA_L_PART(code)/8].uint_value = st.value_1.uint_value;
                        }
                        break;
                    case 1:
                        {
                            int c = code % 4;
                            head->tree[LMT_SA_H_PART(code)][LMT_SA_M_PART(code)][LMT_SA_L_PART(code)/4].uchar_value[c] = st.value_1.uchar_value[c];
                        }
                        break;
                    case 2:
                        {
                            int c = code % 2;
                            head->tree[LMT_SA_H_PART(code)][LMT_SA_M_PART(code)][LMT_SA_L_PART(code)/2].ushort_value[c] = st.value_1.ushort_value[c];
                        }
                        break;
                    case 4:
                        {
                            head->tree[LMT_SA_H_PART(code)][LMT_SA_M_PART(code)][LMT_SA_L_PART(code)] = st.value_1;
                        }
                        break;
                    case 8:
                        {
                            int l = 2 * LMT_SA_L_PART(code);
                            head->tree[LMT_SA_H_PART(code)][LMT_SA_M_PART(code)][l] = st.value_1;
                            head->tree[LMT_SA_H_PART(code)][LMT_SA_M_PART(code)][l+1] = st.value_2;
                        }
                        break;

                }
            }
            --head->sa_stack_ptr;
        }
    }
}

void sa_reinit_stack(const sa_tree head, int level)
{
    if (head->stack) {
        /*tex Stack slot 0 is a dummy. */
        for (int i = head->sa_stack_ptr; i > 0 ; i--) {
            sa_stack_item st = head->stack[i];
            if (st.level > 0) {
             // printf("reinit %i: code %i, v1 %i, v2 %i, level %i\n",head->identifier,st.code,st.value_1.int_value,st.value_2.int_value,st.level);
                switch (head->bytes) {
                    case 0 : sa_set_item_0(head, st.code, st.value_1.uint_value, level); break;
                    case 1 : sa_set_item_1(head, st.code, st.value_1.uchar_value[st.code%4], level); break;
                    case 2 : sa_set_item_2(head, st.code, st.value_1.ushort_value[st.code%2], level); break;
                    case 4 : sa_set_item_4(head, st.code, st.value_1, level); break;
                }
            }
        }
    }
}

void sa_show_stack(const sa_tree head)
{
    if (head->stack) {
        /*tex Stack slot 0 is a dummy. */
        tex_print_format("[codestack %i, size %i]\n", head->identifier, head->sa_stack_ptr - 1);
        for (int i = 1; i <= head->sa_stack_ptr; i++) {
            sa_stack_item st = head->stack[i];
            int value = 0;
            switch (head->bytes) {
                case 0 : value = get_nibble(st.value_1.int_value,st.code); break;
                case 1 : value = st.value_1.uchar_value[st.code%4]; break;
                case 2 : value = st.value_1.ushort_value[st.code%2]; break;
                case 4 : value = st.value_1.int_value; break;
            }
            tex_print_format("%l[%i: level %i, code %i, value %i]\n",i, st.level,st.code,value);
        }
        tex_print_format("%l[codestack %i bottom]\n", head->identifier);
    }
}

void sa_dump_tree(dumpstream f, sa_tree a)
{
    unsigned char bytes = (unsigned char) a->bytes;
    dump_int(f, a->identifier);
    dump_int(f, a->sa_stack_step);
    dump_int(f, a->dflt.int_value);
    /*tex A marker: */
    dump_via_uchar(f, 1);
    dump_via_uchar(f, bytes);
    for (int h = 0; h < LMT_SA_HIGHPART; h++) {
        if (a->tree[h]) {
            for (int m = 0; m < LMT_SA_MIDPART; m++) {
                if (a->tree[h][m]) {
                    /*tex
                        It happens a lot that the value is the same as the index, for instance
                        with case mappings.

                        Using mode 3 for the case where all values are the default value saves
                        In \CONTEXT\ some 128 * 5 dumps which is not worth the trouble but it
                        is neat anyway.

                        1 : values are kind of unique
                        2 : for all values : value == self
                        3 : for all values : value == default

                        Actually, we could decide not to save at all in the third mode because
                        unset equals default.
                    */
                    unsigned char mode = 1;
                    if (bytes != 8) {
                        /*tex Check for default values. */
                        int slide = bytes == 0 ? LMT_SA_LOWPART/8 : (bytes == 1 ? LMT_SA_LOWPART/4 : (bytes == 2 ? LMT_SA_LOWPART/2 : LMT_SA_LOWPART));
                        mode = 3;
                        for (int l = 0; l < slide; l++) {
                            if (a->tree[h][m][l].uint_value != a->dflt.uint_value) {
                                mode = 1;
                                break;
                            }
                        }
                    }
                    if (mode == 1 && bytes == 4) {
                        /*tex Check for identity values. */
                        unsigned int hm = h * LMT_SA_HIGHPART + m * LMT_SA_MIDPART * LMT_SA_LOWPART ;
                        mode = 2;
                        for (int l = 0; l < LMT_SA_LOWPART; l++) {
                            if (a->tree[h][m][l].uint_value == hm) {
                                hm++;
                            } else {
                                mode = 1;
                                break;
                            }
                        }
                    }
                    dump_via_uchar(f, (unsigned char) h);
                    dump_via_uchar(f, (unsigned char) m);
                    dump_uchar(f, mode);
                    if (mode == 1) {
                        /*tex
                            We have unique values. By avoiding this branch we save some 85 Kb
                            on the \CONTEXT\ format. We could actually save this property in
                            the tree but there is not that much to gain.
                        */
                        int slide = LMT_SA_LOWPART;
                        switch (bytes) {
                            case 0: slide =   LMT_SA_LOWPART/8; break;
                            case 1: slide =   LMT_SA_LOWPART/4; break;
                            case 2: slide =   LMT_SA_LOWPART/2; break;
                            case 4: slide =   LMT_SA_LOWPART  ; break;
                            case 8: slide = 2*LMT_SA_LOWPART  ; break;
                        }
                        dump_items(f, &a->tree[h][m][0], sizeof(sa_tree_item), slide);
                    } else {
                        /*tex We have a self value or defaults. */
                    }
                }
            }
        }
    }
    dump_via_uchar(f, 0xFF);
    dump_via_uchar(f, 0xFF);
    dump_via_uchar(f, 0xFF);
}

sa_tree sa_undump_tree(dumpstream f)
{
    unsigned char marker;
    sa_tree a = (sa_tree) sa_malloc_array(sizeof(sa_tree_head), 1);
    undump_int(f, a->identifier);
    undump_int(f, a->sa_stack_step);
    undump_int(f, a->dflt.int_value);
    a->sa_stack_size = a->sa_stack_step;
    a->stack = sa_calloc_array(sizeof(sa_stack_item), a->sa_stack_size);
    a->sa_stack_ptr = 0;
    sa_wipe_array(a->tree, sizeof(sa_tree_item **), LMT_SA_HIGHPART);
    /*tex The marker: */
    undump_uchar(f, marker);
    if (marker != 0) {
        unsigned char bytes;
        undump_uchar(f, bytes);
        a->bytes = bytes;
        while (1) {
            unsigned char h = 0; 
            unsigned char m = 0; 
            unsigned char mode;
            undump_uchar(f, h);
            undump_uchar(f, m);
            undump_uchar(f, mode);
            if (mode == 0xFF) {
                if (h != 0xFF || m != 0xFF) {
                    printf("\nfatal format error, mode %i, bytes %i, high %i, middle %i\n", mode, bytes, h, m);
                    tex_fatal_undump_error("bad sa tree");
                }
                break;
            } else { 
                if (! a->tree[h]) {
                    a->tree[h] = (sa_tree_item **) sa_calloc_array(sizeof(void *), LMT_SA_MIDPART);
                }
                switch (mode) {
                    case 1:
                        /*tex
                            We have a unique values.
                        */
                        {
                            int slide = LMT_SA_LOWPART;
                            switch (bytes) {
                                case 0: slide =   LMT_SA_LOWPART/8; break;
                                case 1: slide =   LMT_SA_LOWPART/4; break;
                                case 2: slide =   LMT_SA_LOWPART/2; break;
                                case 4: slide =   LMT_SA_LOWPART  ; break;
                                case 8: slide = 2*LMT_SA_LOWPART  ; break;
                            }
                            if (! a->tree[h][m]) {
                                a->tree[h][m] = sa_calloc_array(sizeof(sa_tree_item), slide);
                            }
                            undump_items(f, &a->tree[h][m][0], sizeof(sa_tree_item), slide);
                        }
                        break;
                    case 2:
                        /*tex
                            We have a self value. We only have this when we have integers. Other
                            cases are math anyway, so not much to gain.
                        */
                        {
                            if (bytes == 4) {
                                int hm = h * 128 * LMT_SA_HIGHPART + m * LMT_SA_MIDPART;
                                if (! a->tree[h][m]) {
                                    a->tree[h][m] = sa_malloc_array(sizeof(sa_tree_item), LMT_SA_LOWPART);
                                }
                                for (int l = 0; l < LMT_SA_LOWPART; l++) {
                                    a->tree[h][m][l].int_value = hm;
                                    hm++;
                                }
                            } else {
                                printf("\nfatal format error, mode %i, bytes %i\n", mode, bytes);
                                tex_fatal_undump_error("bad sa tree");
                            }
                        }
                        break;
                    case 3:
                        /*tex
                            We have all default values. so no need to set them. In fact, we
                            cannot even end up here.
                        */
                        break;
                    default:
                        /*tex
                            We have no values set.
                        */
                        break;
                }
            }
        }
    }
    return a;
}
