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

// %%COMMENT
// TODO: comment this!!!!

#include <malloc.h>

#include "mat24_functions.h"
#include "clifford12.h"
#include "mm_basics.h"   
#include "mm_op15.h"   
   

// %%IF P != 15
// %%END IF 

// %%IF INT_BITS != 64
// %%END IF 



#define MAX_SHORT_ARRAY 1024

#define V_START 0x200

/// @cond DO_NOT_DOCUMENT 


typedef struct {
    uint_mmv_t v[15468];
    uint_mmv_t vtmp[15468];
    uint_mmv_t vA[(24*32) >> 4];
    uint32_t v_short[MAX_SHORT_ARRAY];
    uint32_t n_short;
    uint64_t basis[24];
} axes_work_t;


/////////////////////////////////////////////////////////////////////////
// Auxiliary functions 
/////////////////////////////////////////////////////////////////////////
 



/**
  @brief Return certain array of short vectors in Leech lattice mod 2

  Let ``v`` be a vector in the rep of the moster mod 15.

  In case ``value1 == 0`` the function computes the list of all 
  vectors ``v2`` in the Leech lattice mod 2 such that the absolute 
  value of the entry of ``v`` corresponding a unit vector labelled 
  by ``v2`` is equal to ``value0``. That list is stored 
  in ``pwork.v_short[j], 0 <= j < ``n_short``.

  In case ``value1 != 0`` wil will store the list of all 
  vectors ``v2`` in the Leech lattice mod 2 such that the absolute 
  value of the entry of ``v`` corresponding a unit vector labelled 
  by ``v2`` is equal to ``value0`` or ``value1` . That list is 
  stored in ``pwork.v_short[j], 0 <= j < ``n_short`` in the same way
  as in function ``mm_op15_axes_find_short``.
*/
 
static inline void get_short(
    uint_mmv_t *v, 
    uint32_t value0, 
    uint32_t value1, 
    axes_work_t *p_work
)
{
    p_work->n_short = mm_op15_axes_find_short(v, p_work->v_short,
                MAX_SHORT_ARRAY,  value0, value1);
}



/**
  Return certain subspace of short vectors in Leech lattice mod 2

  Let ``v`` be a vector in the rep of the moster mod 15, and let
  ``L`` be the set of vectors in the Leech lattice mod 2
  returned by function ``short(v, value)``. The function computes
  the linear subspace of the Leech lattice mod 2 spanned by ``L``
  as a list of vectors. 

  That list is  stored in ``pwork.v_short[j], 0 <= j < ``n_short``.
*/
static inline void get_span(
    uint_mmv_t *v, 
    uint32_t value, 
    axes_work_t *p_work
)
{
    uint32_t dim;
    get_short(v, value, 0, p_work);
    dim = leech2_matrix_basis(p_work->v_short, p_work->n_short, 
                p_work->basis, 24);
    if (dim > 10) dim = 10;
    p_work->n_short = leech2_matrix_expand(p_work->basis, dim, 
          p_work->v_short);
}


/**
  Return certain subspace of short vectors in Leech lattice mod 2

  Let ``v`` be a vector in the rep of the moster mod 15, and let
  ``L`` be the set of vectors in the Leech lattice mod 2
  returned by function ``short(v, value)``. let ``V`` be the
  linear space spanend by ``L``. 

  The function returns the radical of ``V`` (i.e. the intersection
  of ``V`` with its orthogonal complement) as a list of vectors. 

  That list is  stored in ``pwork.v_short[j], 0 <= j < ``n_short``.
*/
static inline void get_radical(
    uint_mmv_t *v, 
    uint32_t value, 
    axes_work_t *p_work
)
{
    uint32_t dim;
    get_short(v, value, 0, p_work);
    dim = leech2_matrix_radical(p_work->v_short, p_work->n_short, 
                p_work->basis, 24);
    if (dim > 10) dim = 10;
    p_work->n_short = leech2_matrix_expand(p_work->basis, dim, 
          p_work->v_short);
}



static inline uint32_t find_type4(
    axes_work_t *p_work
)
{
    uint32_t *pv;
    for (pv = p_work->v_short; pv < p_work->v_short + p_work->n_short; ++pv) {
        if ((gen_leech2_type(*pv & 0xffffff) >> 4) == 4) return *pv;
    }
    return 0;
}


static inline uint32_t find_ortho_short(
    axes_work_t *p_work
)
{
    uint32_t *pv;
    for (pv = p_work->v_short; pv < p_work->v_short + p_work->n_short; ++pv) {
        uint32_t v = *pv & 0xffffff;
        if ((gen_leech2_type(v) >> 4) == 4 &&
           (gen_leech2_type(v ^ V_START) >> 4) == 2) return v ^ V_START;
    }
    return 0;
}



static inline uint32_t xor_entries(
    axes_work_t *p_work,
    int32_t value
)
{
    uint32_t *pv;
    for (pv = p_work->v_short; pv < p_work->v_short + p_work->n_short; ++pv) {
        *pv ^= value;
    }
    return 0;
}

 

static inline uint32_t eval_A_vstart(uint_mmv_t *v)
{
    return mm_op15_eval_A(v, 0x200);
}


/// @endcond  

/////////////////////////////////////////////////////////////////////////
// Stor standard 2A axes in a vector
/////////////////////////////////////////////////////////////////////////


// %%EXPORT px
void  mm_op15_store_axis(uint_mmv_t *v, uint32_t sign)
{
    mm_aux_zero_mmv(15, v);
    static uint32_t sp[4] = {
        (1 << 25) + (2 << 14) + (2 << 8) + 1,
        (1 << 25) + (2 << 14) + (3 << 8) + 15 - 1,
        (1 << 25) + (3 << 14) + (3 << 8) + 1,
        (2 << 25) + (2 << 14) + (3 << 8) 
    };
    sp[3] &= 0xffffff00;
    sp[3] += sign ? 2 : 15 - 2;
    mm_aux_mmv_set_sparse(15, v, sp, 4);
}

/////////////////////////////////////////////////////////////////////////
// Reducing a 2A axis to the standard axis V_START
/////////////////////////////////////////////////////////////////////////



static int32_t reduce_v_axis(uint_mmv_t *v, uint32_t * r, axes_work_t *p_work)
{
    int32_t status = -1000;
    uint32_t len_r = 0, i, j, len_r1, vt, v4, ax_type, e;
    uint32_t ax_types[3];

    for (i = 0; i < 5; ++i) {
        vt = mm_op15_2A_axis_type(v);
        if (vt == 0)  return -2;
        ax_types[1] = ax_types[2] = 0;
        ax_type = vt >> 24;
        vt &= 0xffffff;
        switch(ax_type) {
            case 0xC3:
                get_radical(v, 7, p_work);
                v4 = find_type4(p_work);
                ax_types[0] = 0x42;
                ax_types[1] = 0x61;
                break;
            case 0xA2:
                get_radical(v, 4, p_work);
                v4 = find_type4(p_work);
                ax_types[0] = 0x42;
                ax_types[1] = 0x43;
                break;
            case 0xA1:
                get_short(v, 3, 1, p_work);
                xor_entries(p_work, p_work->v_short[0]);
                v4 = find_type4(p_work);
                ax_types[0] = 0x61;
                break;
            case 0x82:
                get_short(v, 1, 0, p_work);
                xor_entries(p_work, p_work->v_short[0]);
                v4 = find_type4(p_work);
                ax_types[0] = 0x41;
                break;
            case 0x66:
                get_radical(v, 7, p_work);
                v4 = find_type4(p_work);
                ax_types[0] = 0x43;
                break;
            case 0x63:
                get_span(v, 3, p_work);
                v4 = find_type4(p_work);
                ax_types[0] = 0x41;
                break;
            case 0x61:
                get_short(v, 5, 0, p_work);
                xor_entries(p_work, vt);
                v4 = find_type4(p_work);
                ax_types[0] = 0x41;
                break;
            case 0x43:  // case 4C
            case 0x42:  // case 4B
                get_radical(v, 1, p_work);
                v4 = find_type4(p_work);
                ax_types[0] = 0x22;
                break;
            case 0x41:  // case 4A
                v4 = vt;
                ax_types[0] = 0x21;
                break;
            case 0x22:  // case 2B
                get_span(v, 4, p_work);
                v4 = find_type4(p_work);
                ax_types[0] = 0x21;
                break;
            case 0x21:   // case 2B
                len_r1 = gen_leech2_reduce_type2(vt, r+len_r);
                if (len_r1 < 0) return -21;
                mm_op15_word(v, r + len_r, len_r1, 1, p_work->vtmp);
                len_r += len_r1;
                vt = mm_aux_get_mmv1(15, v, (24+3)*32 + 2);
                if (vt != 15-2) {
                   r[len_r] = 0xB0000200;
                   mm_op15_word(v, r + len_r, 1, 1, p_work->vtmp);
                   len_r += 1;
                }
                mm_op15_store_axis(p_work->vtmp, 0);
                if (mm_op15_compare(v, p_work->vtmp)) return -22;
                return len_r;
            default:
                return -1;
        }

        len_r1 = gen_leech2_reduce_type4(v4, r + len_r);
        if (len_r1 < 0) return -10;
        mm_op15_word(v, r + len_r, len_r1, 1, p_work->vtmp);
        len_r += len_r1;
        status = -4;
        for (e = 1; e < 3 && status; ++e) {
            mm_op15_t_A(v, e, p_work->vA);                    
            ax_type = mm_op15_2A_axis_type(p_work->vA) >> 24;
            for (j = 0; j < 3 && ax_types[j]; ++j) {
                if (ax_type == ax_types[j]) {
                    r[len_r] = 0xD0000003 - e;
                    mm_op15_word(v, r + len_r, 1, 1, p_work->vtmp);
                    len_r += 1;
                    status = 0;
                    break;
                }
            }
        }
        if (status)  return status;
    }
    return -3;
}



// %%EXPORT px
int32_t mm_op15_reduce_v_axis(uint_mmv_t *v, uint32_t * r)
{
    int32_t res;
    axes_work_t *p_work = calloc(1, sizeof(axes_work_t));
    if (p_work == 0) return -1;
    res =  reduce_v_axis(v,  r, p_work);
    free(p_work);
    return res;
}


// %%EXPORT px
int32_t mm_op15_reduce_axis(uint32_t *a,  uint32_t n, uint32_t *r)
{
    int32_t res;
    axes_work_t *p_work = calloc(1, sizeof(axes_work_t));
    if (p_work == 0) return -1;
    mm_op15_store_axis(p_work->v, 0);
    res = mm_op15_word(p_work->v, a, n, 1, p_work->vtmp);
    if (res >= 0)  res = reduce_v_axis(p_work->v, r, p_work);
    free(p_work);
    return res;
}



/////////////////////////////////////////////////////////////////////////
// Reducing a 2A axis to orthogonal to V_START, preserving V_START
/////////////////////////////////////////////////////////////////////////


static int32_t reduce_v_baby_axis(uint_mmv_t *v, uint32_t * r, axes_work_t *p_work)
{
    int32_t status = -1000;
    uint32_t len_r = 0, i, j, len_r1, vt, v4, ax_type, e, ind;
    uint32_t ax_types[3];

    for (i = 0; i < 5; ++i) {
        vt = mm_op15_2A_axis_type(v);
        if (vt == 0)  return -2;
        ax_types[1] = ax_types[2] = 0;
        ax_type = vt >> 24;
        vt &= 0xffffff;
        switch(ax_type) {
            case 0xA1:
                get_short(v, 3, 1, p_work);
                xor_entries(p_work, p_work->v_short[0]);
                v4 = find_ortho_short(p_work);
                ax_types[0] = 0x61;
                break;
            case 0x63:
                get_span(v, 3, p_work);
                v4 = find_ortho_short(p_work);
                ax_types[0] = 0x41;
                break;
            case 0x61:
                get_short(v, 5, 0, p_work);
                xor_entries(p_work, vt);
                v4 = find_ortho_short(p_work);
                ax_types[0] = 0x41;
                break;
            case 0x43:  // case 4C
            case 0x42:  // case 4B
                get_radical(v, 1, p_work);
                v4 = find_ortho_short(p_work);
                ax_types[0] = 0x22;
                break;
            case 0x41:  // case 4A
                v4 = vt ^ V_START;
                ax_types[0] = 0x21;
                break;
            case 0x22:  // case 2B
                if ((eval_A_vstart(v) & ~8) == 0) {
                    get_span(v, 4, p_work);
                    v4 = find_ortho_short(p_work);
                    ax_types[0] = 0x21;
                } else return -4;
                break;
            case 0x21:   // case 2B
                if (eval_A_vstart(v) == 0)  {
                    len_r1 = gen_leech2_reduce_type2_ortho(vt, r+len_r);
                    if (len_r1 < 0) return -21;
                    mm_op15_word(v, r + len_r, len_r1, 1, p_work->vtmp);
                    len_r += len_r1;
                    vt = mm_op15_2A_axis_type(v) & 0xffffff;
                    if (vt != 0x800200) return -22;
                    ind = mm_aux_get_mmv1(15, v, (2*24+3)*32 + 2);
                    e = 2 - (ind == 15-2);
                    r[len_r] = 0xD0000003 - e;
                    mm_op15_word(v, r + len_r, 1, 1, p_work->vtmp);
                    len_r += 1;
                }
                mm_op15_store_axis(p_work->vtmp, 1);
                if (mm_op15_compare(v, p_work->vtmp)) return -23;
                return len_r;
            default:
                return -1;
       }

        len_r1 = gen_leech2_reduce_type2_ortho(v4, r + len_r);
        if (len_r1 < 0) return -10;
        mm_op15_word(v, r + len_r, len_r1, 1, p_work->vtmp);
        len_r += len_r1;
        status = -4;
        for (e = 1; e < 3 && status; ++e) {
            mm_op15_t_A(v, e, p_work->vA);                    
            ax_type = mm_op15_2A_axis_type(p_work->vA) >> 24;
            for (j = 0; j < 3 && ax_types[j]; ++j) {
                if (ax_type == ax_types[j]) {
                    r[len_r] = 0xD0000003 - e;
                    mm_op15_word(v, r + len_r, 1, 1, p_work->vtmp);
                    len_r += 1;
                    status = 0;
                    break;
                }
            }
        }
        if (status)  return status;
    }
    return -3;
}



// %%EXPORT px
int32_t mm_op15_reduce_v_baby_axis(uint_mmv_t *v, uint32_t * r)
{
    int32_t res;
    axes_work_t *p_work = calloc(1, sizeof(axes_work_t));
    if (p_work == 0) return -1;
    res =  reduce_v_baby_axis(v,  r, p_work);
    free(p_work);
    return res;
}




// %%EXPORT px
int32_t mm_op15_reduce_G_x0(uint32_t *a,  uint32_t n, uint32_t *r)
{
    int32_t len1, res;
    axes_work_t *p_work = calloc(1, sizeof(axes_work_t));
    if (p_work == 0) return -1;
    mm_op15_store_axis(p_work->v, 0);
    res = mm_op15_word(p_work->v, a, n, 1, p_work->vtmp);
    if (res <  0) goto done;
    len1 = res = reduce_v_axis(p_work->v, r, p_work);
    if (res <  0) goto done;
    mm_op15_store_axis(p_work->v, 1);
    res = mm_op15_word(p_work->v, a, n, 1, p_work->vtmp);
    if (res <  0) goto done;
    res = mm_op15_word(p_work->v, r, len1, 1, p_work->vtmp);
    if (res <  0) goto done;
    res = reduce_v_baby_axis(p_work->v, r + len1, p_work);
    if (res <  0) goto done;
    res = len1 + res;

done:
    free(p_work);
    return res;
}


/**
  @brief Reduce an element in the monster group

  To be documented later!

  Caution:

  This function requires that an order ``v0`` has been stored by
  a previous call to function ``mm_op15_store_order_vector``.
  This is the same precondtion as for 
  function ``mm_op15_order_check_in_Gx0``.
   
*/
// %%EXPORT px
int32_t mm_op15_reduce_M(uint32_t *a,  uint32_t n, uint32_t *r)
{
    int32_t len1, len2, res;

    // This function combines functions  mm_op15_reduce_to_G_x0 and
    // mm_op15_order_check_in_Gx0. Here we cut and paste the code of both 
    // functions, for reducing  the overhead of memory allocation.
    // First allocate memory for both subfunctions.
    axes_work_t *p_work = calloc(1, sizeof(axes_work_t));
    if (p_work == 0) return -1;

    // Cut an paste the operation of function mm_op15_reduce_to_G_x0
    mm_op15_store_axis(p_work->v, 0);
    res = mm_op15_word(p_work->v, a, n, 1, p_work->vtmp);
    if (res <  0) goto done;
    len1 = res = reduce_v_axis(p_work->v, r, p_work);
    if (res <  0) goto done;
    mm_op15_store_axis(p_work->v, 1);
    res = mm_op15_word(p_work->v, a, n, 1, p_work->vtmp);
    if (res <  0) goto done;
    res = mm_op15_word(p_work->v, r, len1, 1, p_work->vtmp);
    if (res <  0) goto done;
    res = reduce_v_baby_axis(p_work->v, r + len1, p_work);
    if (res <  0) goto done;
    len1 = res = len1 + res;

    // Let ``g1`` be the element of the monster stored in
    // ``r[0,...,len1-1]``. Then ``h := g * g1`` is now in the
    // Subgroup ``G_x0`` of the monster.

    // Put p_work->v = v0 * h
    if (mm_op15_load_order_vector(p_work->v) < 0) return -1; 
    res = mm_op15_word(p_work->v, a, n, 1, p_work->vtmp);
    if (res < 0) goto done;
    res = mm_op15_word(p_work->v, r, len1, 1, p_work->vtmp);
    if (res < 0) goto done;

    // Now we essentially apply function t mm_op15_order_check_in_Gx0
    // to find a representation of ``h`` in generators of ``G_x0``.
    res = mm_op15_order_find_in_Gx0(p_work->v, r + len1);
    if (res > 0x100) res = -0x200;
    if (res < 0) goto done; 
    len2 = res = mm_op15_order_find_in_Qx0(p_work->v, r + len1, 
          p_work->vtmp);
    if (res < 0) goto done;

    // Now ``r[len1,...,len1+len2-1]``  contains ``h``. We will compute
    // the result ``h * g1**(-1)`` in ``r[0,...,len1+len2-1]`` and
    // return res = len1 + len2.
    mm_group_invert_word(r + len1, len2);
    mm_group_invert_word(r, res = len1 + len2);
  
done:
    free(p_work);
    return res;
}
