/////////////////////////////////////////////////////////////////////////////
// This C file has been created automatically. Do not edit!!!
/////////////////////////////////////////////////////////////////////////////

/** @file mm_shorten.c

 This file contains function for reducing a word of generators
 of the monster group. Here word reduction is done by computations
 in the subgroups \f$G_{x0} = 2^{1+24}.\mbox{Co}_1\f$
 and \f$N_0 = 2^{2+11+22}.(\mbox{Mat}_{24} \times \mbox{Sym}_3)\f$.
 To some extent, we will also compute in the Frobenius group
 of order 21 generated by the generators \f$\tau\f$ and \f$\xi\f$
 of the Monster.
 
 Internally, we store a word ``a`` of generators of the Monster in
 a structure of type ``gt_subword_type``. Here the word ``a`` is
 split into subwords, where each subword is in the double
 coset \f$G_{x0} N_0\f$. Such a subword is represented as a word
 of generators of  \f$G_{x0}\f$, followed by a power of the
 triality element \f$\xi\f$.

 Unless otherwise stated, a negative return value of a function
 is this module indicated an error.

 Structues required for this module are defined in 
 file ``mm_reduce.h``.

 Warning:

 This module is yet experimental and not for public use!

*/

/// @cond DO_NOT_DOCUMENT 
#include "mm_reduce.h"   
/// @endcond 
   
// %%EXPORT_KWD MM_REDUCE_API




/////////////////////////////////////////////////////////////////////////
// Definitions of internally-used structures
/////////////////////////////////////////////////////////////////////////


//  %%GEN h
// %%GEN ch
#ifdef __cplusplus
extern "C" {
#endif
// %%GEN c



//  %%GEN h
//  %%GEN c


/////////////////////////////////////////////////////////////////////////
// Definitions in the C file
/////////////////////////////////////////////////////////////////////////

/// @cond DO_NOT_DOCUMENT 

#define SIZE_GT_WORD_DATA(n) (sizeof(gt_word_type) + (n)*sizeof(gt_subword_type))

#define OMEGA 0x800000

#define _ERROR(source, error) ((-(source) << 24) - ((-(error)) & 0xffffff)) 


/// @endcond  


/////////////////////////////////////////////////////////////////////////
// Creating and manipulating the doubly linked list
/////////////////////////////////////////////////////////////////////////




/// @cond DO_NOT_DOCUMENT 


/**
@brief Yet to be documented!

Set a subword to the empty subword.

*/
// %%EXPORT p
MM_REDUCE_API
void gt_subword_clear(gt_subword_type *p_gtsub)
{
    p_gtsub->length = p_gtsub->t_exp = 0; 
    p_gtsub->img_Omega =  OMEGA;
    p_gtsub->reduced = 1; 
} 

/// @endcond  



/**
@brief Yet to be documented!

This function initilizes an (existing) buffer referred by
parameter ``p_gtw`` as a pointer to a valid structure of
type ``gt_word_type``, so that up to ``n`` subwords can be
stored in the nodes belonging to that structure.

Here the nodes belonging to that structure are stored in the
same memory block as the header (of type ``gt_word_type``) of
that structure. The maximum number ``n`` of subwords to be
stored must be known when calling this function.

The minimum number of bytes required for the buffer referred
by ``p_gtw`` is equal to ``gt_word_size(n)``.

On exit the structure contains the empty word.
*/
// %%EXPORT p
MM_REDUCE_API
void gt_word_init(gt_word_type *p_gtw, uint32_t n)
{
    uint32_t i;
    p_gtw->n_subwords = 0;
    for (i = 0; i <= n; ++i) {
       p_gtw->a[i].eof = 0;
       p_gtw->a[i].p_next = &(p_gtw->a[i + 1]);
    }
    p_gtw->a[n].p_next = NULL;
    p_gtw->p_free = p_gtw->a[0].p_next;
    p_gtw->p_end = p_gtw->p_node = p_gtw->a[0].p_next = 
        p_gtw->a[0].p_prev = p_gtw->a;
    p_gtw->a[0].eof = 1;

    gt_subword_clear(p_gtw->a+0);
    p_gtw->n_subwords = n;
} 



/**
@brief Yet to be documented!

Return the number of bytes required by the buffer given as
store a parameter to function ``gt_word_init`` in order to
``n`` subwords. 
*/
// %%EXPORT p
MM_REDUCE_API
uint32_t gt_word_size(uint32_t n) 
{
    return sizeof(gt_word_type) + n*sizeof(gt_subword_type);
}




/**
@brief Yet to be documented!

Insert an empty subword after the current subword,
set the position of the current subword to the new subword.

Return a negative value if out of memory
*/
// %%EXPORT p
MM_REDUCE_API
int32_t gt_word_insert(gt_word_type *p_gt)
{
    gt_subword_type *p_new, *p_cur, *p_next;

    if ((p_new = p_gt->p_free) == NULL) return _ERROR(1, -1);
    p_gt->p_free = p_new->p_next;
    gt_subword_clear(p_new);

    p_cur = p_gt->p_node;
    p_next = p_cur->p_next;
    p_new->p_next = p_next;
    p_new->p_prev = p_cur;
    p_cur->p_next = p_next->p_prev = p_gt->p_node = p_new;
    return 0;  
}

/**
@brief Yet to be documented!

Delete the current subword, and
set the position of the current subword to its predecessor.

Return a negative value on an attempt to delete an EOF mark
*/
// %%EXPORT p
MM_REDUCE_API
int32_t gt_word_delete(gt_word_type *p_gt)
{
    gt_subword_type *p_prev, *p_cur, *p_next;
 
    if ((p_cur = p_gt->p_node)->eof) return _ERROR(1, -2);
    p_next = p_cur->p_next;
    p_prev = p_cur->p_prev;

    p_gt->p_node = p_next->p_prev = p_prev;
    p_prev->p_next = p_next;

    p_cur->p_next = p_gt->p_free;
    p_gt->p_free = p_cur;

    return 0;  
}




/**
@brief Yet to be documented!

Move the pointer to the current subword to position 'pos'
Her pos = 0 means the EOF mark if 'set' is True,
and the position of the current subword otherwise. 
pos = i+1 means the subword after  pos = i,
pos = i-1 means node subword  pos = i.


It is an error to move p_gt->p_node across the EOF node.
When starting at the EOF node we may move forward or back
until finding the EOF node once again, but not any further.
*/
// %%EXPORT p
MM_REDUCE_API
int32_t gt_word_seek(gt_word_type *p_gt, int32_t pos, uint32_t set)
{
    int32_t i;
    if (set) p_gt->p_node = p_gt->p_end;
    if (pos > 0) {
        for (i = 1; i <= pos; ++i) {
            p_gt->p_node = p_gt->p_node->p_next;
            if (p_gt->p_node->eof && pos != i) return _ERROR(1, -3);
        }
    } else if (pos < 0) {
        for (i = -1; i >= pos; --i) {
            p_gt->p_node = p_gt->p_node->p_prev;
            if (p_gt->p_node->eof && pos != i) return _ERROR(1, -4);
        }
    }
    return 0;
}






/////////////////////////////////////////////////////////////////////////
// Append part of a monster group element to a subword
/////////////////////////////////////////////////////////////////////////

/// @cond DO_NOT_DOCUMENT 
#define MAX_INTERN_WORD_DATA (MAX_GT_WORD_DATA+40)

/// @endcond






/**
@brief Yet to be documented!


Return number of subwords in a doubly linked list of 
type ``gt_word_type`` required to store the word of generators
of the Monster of length ``n`` referred by ``a``.
*/
// %%EXPORT px
MM_REDUCE_API
uint32_t gt_word_n_subwords(uint32_t *a, uint32_t n)
{
    uint32_t i, k = 1;
    for (i = 0; i < n; ++i) if ((a[i] & 0x70000000) == 0x50000000) ++k;
    return k;
}



/**
@brief Yet to be documented!

Try to append a prefix of word 'a' of length 'n' to the current subword.
Return number of atoms of 'a' sucessfully appended to that word. 
A negative return value inducates an error.
*/
// %%EXPORT p
MM_REDUCE_API
int32_t gt_word_append_sub_part(gt_word_type *p_gt, uint32_t *a,  uint32_t n)
{
    
    gt_subword_type *p_cur = p_gt->p_node;
    uint32_t i = 0, ulen = 0, img_omega_len = 0, j, e, v;
    uint32_t reduced = p_cur->reduced;
    uint32_t w[MAX_INTERN_WORD_DATA], g[5];
    int32_t res;

    if (p_cur->eof) return 0;
    img_omega_len = ulen = p_cur->length;
    e = p_cur->t_exp;
    for (j = 0; j < ulen; ++j) w[j] = p_cur->data[j];
    
    while (1)  {
        mm_group_n_clear(g);
        g[0] = e;
        i += mm_group_n_mul_word_scan(g, a + i, n - i);
        e = mm_group_n_right_coset_N_x0(g);
        j = mm_group_n_to_word(g, w + ulen);
        reduced &= !j;
        ulen += j;
        if (e || i >= n) break;
        v = a[i++];
        if ((v & 0x70000000) != 0x60000000) return _ERROR(2,-1);
        v ^=  0 - ((v >> 31) & 1);
        v = (v & 0xfffffff) % 3;
        if (v) {
            w[ulen++] = v + 0x60000000;
            reduced = 0;
        }
        if (ulen > MAX_INTERN_WORD_DATA - 6) {
            if ((res = xsp2co1_reduce_word(w, ulen, w)) < 0) 
                   return _ERROR(-0x10, res);
            ulen = res;
            img_omega_len = 0;
            p_cur->img_Omega = OMEGA;
            reduced = 1;
        }
    }
    p_cur->t_exp = e;
    if (ulen > MAX_GT_WORD_DATA - 1) {
       if ((res = xsp2co1_reduce_word(w, ulen, w)) < 0) 
          return _ERROR(-0x11, res);
       ulen = res;
       img_omega_len = 0;
       p_cur->img_Omega = OMEGA;
       reduced = 1;
    }
    p_cur->img_Omega = gen_leech2_op_word_leech2(p_cur->img_Omega, 
        w + img_omega_len, ulen - img_omega_len, 0);
    p_cur->length = ulen;
    p_cur->reduced = reduced;
    for (j = 0; j < ulen; ++j) p_cur->data[j] = w[j];
    return i;
}





/**
Append the word 'a' of length 'n' at the end of the current subword.
Set the current subword to the last word before the appended word 'a'.

A negative return value inducates an error.
*/
// %%EXPORT p
MM_REDUCE_API
int32_t gt_word_append(gt_word_type *p_gt, uint32_t *a, uint32_t n)
{
    int32_t res;
    uint32_t i = 0;
    gt_subword_type *p_cur = p_gt->p_node = p_gt->p_end->p_prev;
    while (i < n) {
        if ((res = gt_word_insert(p_gt)) < 0) return res;
        if ((res = gt_word_append_sub_part(p_gt, a + i,  n - i)) <= 0) 
            return res < 0 ? res : _ERROR(2,-3);
        i += res;
    } 
    p_gt->p_node = p_cur;
    return 0;    
}


/////////////////////////////////////////////////////////////////////////
// Reduce a subword
/////////////////////////////////////////////////////////////////////////


/// @cond DO_NOT_DOCUMENT 

/**
@brief Yet to be documented!

Compute 'complexity' of an element \f$g\f$ of \f$G_{x0}\f$ from
the image of \f$\Omega\f$  under \f$g\f$ .

Here the complexity of \f$g\f$ is the minimum number of atoms
of tag ``l`` required to represent \f$g\f$. 
*/
static inline uint32_t complexity(uint32_t img_omega)
{
    uint32_t weight;
    if (img_omega & 0x800) return 3;
    if ((img_omega & 0x7ff800) == 0) return (img_omega & 0x7fffff) != 0;
    weight = (MAT24_THETA_TABLE[(img_omega >> 12) & 0x7ff] >> 12) & 1;
    return 2 + weight;
}

/**
@brief Yet to be documented!

Return True if an element  \f$g\f$ of \f$G_{x0}\f$ stored in the
array of ``a`` of length ``n`` is so complex that lazy reduction
is worth the effort.

Here Parameter ``l_weight`` is the output of function ``complexity``
applied to \f$g\f$. 
*/
static inline 
uint32_t over_complex(uint32_t *a, uint32_t n, uint32_t l_weight)
{
    uint32_t n_atoms[8], i;

    if (n > 12) return 1;

    for (i = 0; i < 8; ++i) n_atoms[i] = 0;
    for (i = 0; i < n; ++i) ++(n_atoms[(a[i] >> 28) & 7]);
    if (n_atoms[6] > l_weight) return 1;        // tag 'l'
    if (n_atoms[2] > l_weight + 1) return 1;    // tag 'p'
    if (n_atoms[3] > 1) return 1;               // tag 'x'
    if (n_atoms[4] > 1) return 1;               // tag 'y'
    if (n_atoms[1] > 2) return 1;               // tag 'd'
    return 0;
}


/// @endcond


/**
@brief Yet to be documented!

Reduce current subword

mode = 0: lazy reduction (just some word shortening)

mode = 1: standard reduction

mode = 2: as mode 0, but move also prefix in N_x0 to previous subword

mode = 3: as mode 1, but move also prefix in N_x0 to previous subword
*/
// %%EXPORT p
MM_REDUCE_API
int32_t gt_word_reduce_sub(gt_word_type *p_gt, uint32_t mode)
{
    gt_subword_type *p_cur = p_gt->p_node;
    uint32_t *w;
    int32_t res;
    uint32_t i, j;
    uint32_t length = p_cur->length;
    if (p_cur->eof || length < 2) {
        p_cur->reduced = 1;
        return 0;
    }
    w = p_cur->data;
    if (!p_cur->reduced) {
        uint32_t reduce = mode & 1;
        if (reduce == 0) {
            uint32_t l_weight = complexity(p_cur->img_Omega);
            reduce = over_complex(w, length,l_weight); 
        }
        if (reduce) {
            res = xsp2co1_reduce_word(w, length, w);
            if (res < 0) return _ERROR(-0x12, res);
            p_cur->length = res;
            p_cur->reduced = 1;
        }
    }
    if (mode < 2 || p_cur->p_prev->eof) return 0;
    for (i = 0; i < length; ++i) {
       if ((w[i] & 0x70000000) == 0x60000000) break;
    }
    if (i) {
        p_gt->p_node = p_cur->p_prev;
        if ((res = gt_word_append_sub_part(p_gt, w, i)) < 0) return res;
        p_gt->p_node = p_cur;
        p_cur->length = length - i;
        for (j = 0; j < p_cur->length; ++j) w[j] = w[i + j];
    }
    return 0;
}


/////////////////////////////////////////////////////////////////////////
// Join a subword with its predecessors if possible
/////////////////////////////////////////////////////////////////////////



/**
@brief Yet to be documented!

Try to join current subwort p_gt->p_node with its predecessor. 
Return 1 and point to (the joined) predecssor if this it possible.
Return 0 and do not change the pointer this it not possible.
Return a negative number in case of a fatal error.
*/ 
// %%EXPORT p
MM_REDUCE_API
int32_t gt_word_rule_join(gt_word_type *p_gt)
{
    gt_subword_type *p_cur, *p_prev;
    int32_t res;

    p_cur = p_gt->p_node;
    if (p_cur->eof) return 0;
    p_prev = p_cur->p_prev;
    if (p_prev->eof) {
        if (p_cur->img_Omega == OMEGA && p_cur->t_exp == 0) {
            res = xsp2co1_reduce_word(
                p_cur->data, p_cur->length, p_cur->data);
            if (res < 0) return _ERROR(-0x14, res);
            if (res == 0) {
                gt_word_delete(p_gt);
                return 1;
            }
            p_cur->length = res;
            p_cur->reduced = 1;
        }
        return 0;
    }

    if (p_prev->t_exp == 0) {
        gt_word_delete(p_gt);
        if (p_gt->p_node != p_prev) return _ERROR(2, -0x104);
        p_cur->data[p_cur->length++] = 0x50000000 + p_cur->t_exp;
        res = gt_word_append_sub_part(p_gt, p_cur->data, p_cur->length);
        if (res != (int32_t)p_cur->length) 
            return res < 0 ? res : _ERROR(2, -4);
        return 1;
    } else if (p_cur->img_Omega == OMEGA) {
        gt_word_delete(p_gt);
        if (p_gt->p_node != p_prev) return _ERROR(2, -0x105);
        res = xsp2co1_reduce_word(
             p_cur->data, p_cur->length, p_cur->data);
        if (res < 0) return _ERROR(-0x14, res);
        p_cur->length = res;
        p_cur->data[p_cur->length++] = 0x50000000 + p_cur->t_exp;
        res = gt_word_append_sub_part(p_gt, p_cur->data, p_cur->length);
        if (res != (int32_t)p_cur->length) 
            return res < 0 ? res : _ERROR(2, -5);
        return 1;
    } 
    return 0;
}








/////////////////////////////////////////////////////////////////////////
// Store a word or a subword as an array of atoms
/////////////////////////////////////////////////////////////////////////



/**
@brief Yet to be documented!

*/
// %%EXPORT p
MM_REDUCE_API
int32_t gt_word_length(gt_word_type *p_gt)
{
    uint32_t length = 0;
    gt_subword_type *p_cur = p_gt->p_end->p_next;
    for (; p_cur->eof == 0; p_cur = p_cur->p_next) {
        length += p_cur->length + (p_cur->t_exp != 0);
    } 
    return length;
}



/**
@brief Yet to be documented!

*/
// %%EXPORT p
MM_REDUCE_API
int32_t gt_word_store(gt_word_type *p_gt, uint32_t *pa, uint32_t maxlen)
{
    uint32_t *p_out = pa, *p_end = pa + maxlen, *p_data, n, e;
    gt_subword_type *p_cur = p_gt->p_end->p_next;
    for (; p_cur->eof == 0; p_cur = p_cur->p_next) {
        e = p_cur->t_exp;
        p_data = p_cur->data;
        p_data[n = p_cur->length] = 0x50000000 + e;
        n += e != 0;
        if  (p_out + n > p_end) return _ERROR(2, -7); 
        while (n--) *p_out++ = *p_data++;
    } 
    return (int32_t)(p_out - pa);
}


/**
@brief Yet to be documented!

*/
// %%EXPORT p
MM_REDUCE_API
int32_t gt_word_store_sub(gt_word_type *p_gt, uint32_t *pa, uint32_t maxlen)
{
    uint32_t  *p_data, n, e, i;
    gt_subword_type *p_cur = p_gt->p_node;
    e = p_cur->t_exp;
    p_data = p_cur->data;
    p_data[n = p_cur->length] = 0x50000000 + e;
    n += e != 0;
    if (n > maxlen) return _ERROR(2, -8); ;
    for (i = 0; i < n; ++i) pa[i] = p_data[i];
    return n;
}




/////////////////////////////////////////////////////////////////////////
// Exported functions for shortening a word
/////////////////////////////////////////////////////////////////////////

/**
@brief Yet to be documented!

*/
// %%EXPORT px
MM_REDUCE_API
int32_t gt_word_shorten(uint32_t *g, uint32_t n, uint32_t *g1, uint32_t n1, uint32_t mode)
{
    gt_word_type* p_gt = NULL;
    uint32_t nsub, n_out;
    int32_t res, status = 0;

    nsub = gt_word_n_subwords(g, n) + 5;
    p_gt = malloc(SIZE_GT_WORD_DATA(nsub)); 
    if (p_gt == NULL) return -1;
    gt_word_init(p_gt, nsub);
    res = gt_word_append(p_gt, g, n);
    status = 2; if (res < 0) goto done;

    res = gt_word_seek(p_gt, 1, 1);
    status = 3; if (res < 0) goto done;
    while (!p_gt->p_node->eof) {
       res = gt_word_rule_join(p_gt);
       status = 4; if (res < 0) goto done;
       if (!res) res = gt_word_seek(p_gt, 1, 0);
       status = 5; if (res < 0) goto done;
    }

    res = gt_word_seek(p_gt, -1, 1);
    status = 6; if (res < 0) goto done;
    while (!p_gt->p_node->eof) {
       res = gt_word_reduce_sub(p_gt, mode);
       status = 7; if (res < 0) goto done;
       res = gt_word_seek(p_gt, -1, 0);
       status = 8; if (res < 0) goto done;
    }

    n_out = gt_word_length(p_gt);
    status = 9; res = -1;
    if (n_out < n1) goto done;
    status = 10;
    res = gt_word_store(p_gt, g1, n1);

  done:
    if (p_gt != NULL) free(p_gt);
    if (res >= 0) return res;
    return (int32_t)(((-status << 24) + res) & 0x80000000);

}

//  %%GEN h
//  %%GEN c


// %%GEN ch
#ifdef __cplusplus
}
#endif

