/////////////////////////////////////////////////////////////////////////////
// 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_op15.h"   
   

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

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

enum tag_offsets {
    OFS_NORM_A = 0,
    OFS_DIAG_VA = 1,
    OFS_WATERMARK_PERM = 2,
    OFS_TAGS_Y = 26,
    OFS_SOLVE_Y = 37,
    OFS_TAGS_X = 48,
    OFS_SOLVE_X = 72,
    OFS_TAG_SIGN = 96,
};


/************************************************************************
** Load ORDER_VECTOR and ORDER_TAG_VECTOR
************************************************************************/

/// @cond DO_NOT_DOCUMENT 

#define LEN_TAG_VECTOR 97

static uint_mmv_t ORDER_VECTOR[15468];

static uint32_t TAG_VECTOR[LEN_TAG_VECTOR];

static uint32_t  ORDER_VECTOR_PRESENT;

#define MAGIC 0xd5cf2469UL

/// @endcond  


/**
  @brief Check if a vector is an image under an element of \f$G_{x0}\f$

  Let ``v0`` be an order vector in the representation \f$\rho_{15}\f$
  of the monster group. This is a vector whose stabilizer in the monster
  is the neutral element, satisfying some additional conditions as
  described in module ``mmgroup.structures.find_mm_order.py``. A suitable 
  order vector is the object ORDER_VECTOR in 
  module ``mmgroup.mm_order.py``. This function store such an order
  vector ``v0`` in an internal buffer.

  The function also requires a parameter ``tags`` describing some
  properties of vector ``v0``, corresponding to the array ``ORDER_TAGS``
  computed in module ``mmgroup.mm_order.py``. The function also stores
  the vector ``tags`` in an internal buffer.

*/
// %%EXPORT px
void mm_op15_store_order_vector(uint32_t *tags, uint64_t *v0)
{
    uint_fast32_t i;
    ORDER_VECTOR_PRESENT = 0;
    for (i = 0; i < 15468; ++i) ORDER_VECTOR[i] = v0[i];
    for (i = 0; i < LEN_TAG_VECTOR; ++i) TAG_VECTOR[i] = tags[i];
    ORDER_VECTOR_PRESENT = MAGIC;   
}



// %%EXPORT px
int32_t mm_op15_load_order_vector(uint64_t *v0)
{
    uint_fast32_t i;
    if (ORDER_VECTOR_PRESENT != MAGIC) return -1;   
    for (i = 0; i < 15468; ++i)  v0[i] = ORDER_VECTOR[i];
    return 0;   
}

// %%EXPORT px
int32_t mm_op15_load_order_tag_vector(uint32_t *tag_vector)
{
    uint_fast32_t i;
    if (ORDER_VECTOR_PRESENT != MAGIC) return -1;   
    for (i = 0; i < LEN_TAG_VECTOR; ++i)  tag_vector[i] = TAG_VECTOR[i];
    return 0;   
}

/************************************************************************
** Check if element of monster group is in subgroup G_x0
************************************************************************/


/**
  @brief Auxiliary function for function ``mm_op15_order_check_in_Gx0``

  Inputs ``v``, and the return value are as in
  function ``mm_op15_order_check_in_Gx0``. A oder vector ``v0`` must
  have been loaded with function ``mm_op15_store_order_vector``.

  This function requires that an order ``v0`` has been stored by
  a previous call to function ``mm_op15_store_order_vector``.

  Assume that ``v`` is an image of ``v0`` under an unknown element
  \f$g\f$ of the monster. We want to find that element \f$g\f$ if
  it is in the subgroup \f$G_{x0}\f$ of the monster.

  The function computes an element \f$g_1\f$ such
  that \f$g^{-1} g_1\in Q_{x0}\f$ holds in case \f$g \in G_{x0}\f$.
  If \f$g \notin G_{x0}\f$  then the function detects this
  fact with high probability.

  In case of success the function writes the element \f$g_1\f$ into the
  array ``g`` is the same way as function ``mm_op15_order_check_in_Gx0``
  writes its result int that array.
  
  The function does not change the input ``v``.  
*/
// %%EXPORT px
int32_t mm_op15_order_find_in_Gx0(uint64_t *v, uint32_t *g)
{
    uint64_t a[48], w3;
    int32_t res, v_y;
    uint_fast32_t w_type4, i, len, perm_num, y;

    if (ORDER_VECTOR_PRESENT != MAGIC) return -0x1000001;

    if ((uint32_t)leech_matrix_norm_A(15, v) 
         != TAG_VECTOR[OFS_NORM_A]) return 0x101;

    w3 = leech3matrix_kernel_vector(15, v, TAG_VECTOR[OFS_DIAG_VA]);
    if (w3 == 0) return 0x102;
    w_type4 = (uint_fast32_t)(gen_leech3to2_type4(w3));
    if (w_type4 == 0) return 0x103;

    res = gen_leech2_reduce_type4(w_type4, g);
    if (res < 0) return res;
    len = res;

    for (i = 0; i < 48; ++i) a[i] = v[i];
    res = mm_op15_word_tag_A(a, g, len, 1);
    if (res < 0) return res;
     
    res = leech3matrix_watermark_perm_num(15, 
        TAG_VECTOR + OFS_WATERMARK_PERM, a);
    if (res < 0) return 0x104;
    perm_num = res;
    if (perm_num) {
        g[len] = 0xA0000000 + perm_num;
        res = mm_op15_word_tag_A(a, g + len, 1, 1);
        if (res < 0) return res;
        len += 1;
    }

    v_y = mm_aux_mmv_extract_sparse_signs(15, a, 
        TAG_VECTOR + OFS_TAGS_Y, 11);
    if (v_y < 0) return 0x105;
    y = leech2matrix_solve_eqn(TAG_VECTOR + OFS_SOLVE_Y, 11, v_y);
    if (y > 0) {
        g[len] = 0xC0000000 + y;
        res = mm_op15_word_tag_A(a, g + len, 1, 1);
        if (res < 0) return res;
        len += 1;
    }

    if (mm_op15_compare_len(ORDER_VECTOR, a, 48)) return 0x106;
    for (i = len; i <10; ++i) g[i] = 0;

    return len;
}






/**
  @brief Auxiliary function for function ``mm_op15_order_check_in_Gx0``

  Inputs ``v``, and the return value are as in
  function ``mm_op15_order_check_in_Gx0``.  Parameter ``g`` must contain 
  the corrsponding output of function ``mm_op15_order_check_in_Gx0``, when
  called successfully with the same input parameters. The function also 
  requires an array `` work`` of length 15468 as a work buffer.

  This function requires that an order ``v0`` has been stored by
  a previous call to function ``mm_op15_store_order_vector``.

  Assume that ``v`` is an image of ``v0`` under an unknown element
  \f$g\f$ of the monster. We want to find that element \f$g\f$ if
  it is in the subgroup \f$G_{x0}\f$ of the monster.

  In case of success the function writes the element \f$g_1\f$ into the
  array ``g`` in the same way as function ``mm_op15_order_check_in_Gx0``
  writes its result int that array. 

  Caution:

  This function destroys the input ``v``!  
*/
// %%EXPORT px
uint32_t mm_op15_order_find_in_Qx0(uint64_t *v, uint32_t *g, uint64_t *work)
{
    int32_t v_x, sign, len = 10, res, len1, i;
    uint_fast32_t x, v_sign, tmp;
    uint32_t aa;
    
    if (ORDER_VECTOR_PRESENT != MAGIC) return -0x1000001;

    while (len && g[len-1] == 0) --len;
    if (g[0]) {
        res = mm_op15_word(v, g, len, 1, work);
        if (res < 0) return res;
    }

    v_x = mm_aux_mmv_extract_sparse_signs(15, v, TAG_VECTOR + OFS_TAGS_X, 24);
    if (v_x < 0) return 0x107;

    x = leech2matrix_solve_eqn(TAG_VECTOR + OFS_SOLVE_X, 24, v_x) &  0xffffff;
    v_sign = ((x >> 12) & 0x7ff) ^ (x & 0x800);
    aa = TAG_VECTOR[OFS_TAG_SIGN] ^ (v_sign << 14);
    sign = mm_aux_mmv_extract_sparse_signs(15, v, &aa, 1);
    if (v_x < 0) return 0x108;

    sign ^= uint64_parity(x & (x >> 12) & 0x7ff);
    x ^=  (sign & 1) << 24;
    x ^= mat24_ploop_theta(x >> 12);
    
    len1 = len;
    if (x & 0xfff) g[len1++] = 0x90000000 + (x & 0xfff); 
    x = (x >> 12) & 0x1fff;
    if (x) g[len1++] = 0xB0000000 + x;

    if (len1 > len) {
        res = mm_op15_word(v, g + len, len1 - len, 1, work);  
        if (res < 0) return res;
    }    
    res = mm_op15_compare(v, ORDER_VECTOR);
    if (res)  return  0x209;
    for (i = 0; i < len1 >> 1; ++i) {
        tmp = g[i]; g[i] = g[len1-1-i]; g[len1-1-i] = tmp; 
    }
    for (i = 0; i < len1; ++i) g[i] ^= 0x80000000;        
    return len1;   
}


/**
  @brief Check if a vector is an image under an element of \f$G_{x0}\f$

  This function requires that an order ``v0`` has been stored by
  a previous call to function ``mm_op15_store_order_vector``.

  Let ``v0`` be that order vector in the representation \f$\rho_{15}\f$
  of the monster group. This is a vector whose stabilizer in the monster
  is the neutral element, satisfying some additional conditions as
  described in module ``mmgroup.structures.find_mm_order.py``. A suitable 
  order vector is the object ORDER_VECTOR in 
  module ``mmgroup.mm_order.py``.

  Assume that ``v`` is an image of ``v0`` under an unknown element
  \f$g\f$ of the monster. The function comutpes the element\f$g\f$ if
  it is in the subgroup \f$G_{x0}\f$ of the monster; otherwise we have
  hardly any chance to find it.

  In case of success the function writes the element \f$g\f$ into the
  array ``g`` as a word generators of the monster group of length at
  most 10; and the function returns the length of that word. If
  inputs ``tags`` and ``v0`` have been  constructed properly then
  the function finds such an element \f$g\f$ if it exists.
  
  If a suitable element \f$g\f$ has been found then function returns
  a nonnegative number less or equal to 10 denoting the actual length
  of the word in the array ``g`` without padding zeros.

  If no such element \f$g_1\f$ has been found then the function returns
  a number greater than 256. Then the exact return value gives some
  indication why no such element \f$g\f$ has been found; this is for
  debugging only.

  A negative return value indicates an error. 

  The function does not change the inputs ``v``.  
*/
// %%EXPORT px
int32_t mm_op15_order_check_in_Gx0(uint64_t *v, uint32_t *g)
{
    int32_t res;
    uint64_t *w, *work;

    res = mm_op15_order_find_in_Gx0(v, g);
    if (res >= 0x100 || res < 0) return res;

    w = calloc(2 * 15468, sizeof(uint64_t));
    if (w == NULL) return ERR_QSTATE12_BUFFER_OVFL;
    work = w + 15468;
    mm_op15_copy(v, w);    
    res = mm_op15_order_find_in_Qx0(w, g, work);
    free(w);
    return res;    
}




/**
  @brief Compute exponent \f$e\f$ such that \f$g^e \in G_{x0}\f$

  Let \f$g\f$ be the element of the monster group stored in the
  array ``g`` as a word of generators of the monster group of
  length ``n``.

  The function computes the smallest exponent \f$e\f$ such
  that  \f$g^e\f$ is in \f$G_{x0}\f$. Then the function
  writes \f$h = g^e\f$ into the buffer ``h`` as a word of
  generators \f$G_{x0}\f$ of length at most 10. Let ``k`` be the
  length of the word representing ``h``. Then the function returns
  the value ``0x100 * e + k``; here we have ``1 <= e <= 119``
  and ``0 <= k <= 10``.
   
  Computation of ``e`` is time consuming, and in some cases we
  are interested in small values of ``e`` only.
  Parameter ``o`` is an upper bound for the exponent ``e``.
  The function returns 0 in case ``e > o``; then the data in
  buffer ``h`` are invalid.

  This function requires that an order vector has been stored by
  a previous call to function ``mm_op15_store_order_vector``.

  A negative return value indicates an error. 
*/
// %%EXPORT px
int32_t mm_op15_order_Gx0(uint32_t *g, uint32_t n, uint32_t *h, uint32_t o)
{
    int32_t res;
    uint_fast32_t i;
    uint64_t *w = NULL, *w1, *work;
    uint64_t elem[26];

    if (ORDER_VECTOR_PRESENT != MAGIC) return -0x1000001;

    if (o < 1) o = 1;
    if (o > 119) o = 119;
    res = xsp2co1_check_word_g_x0(g, n);
    if (res == 0) {
        if (n == 0) {
            return 0x100;
        } else {
            res = xsp2co1_set_elem_word(elem, g, n);
            if (res < 0) return res;
            res = xsp2co1_elem_to_word(elem, h);
            if (res < 0) return res;
            return res + 0x100;
        }
    }
    if (res == 1 && o == 1) return 0;

    w = calloc(3 * 15468, sizeof(uint64_t));
    if (w == NULL) return ERR_QSTATE12_BUFFER_OVFL;
    w1 = w + 15468;
    work = w1 + 15468;

    mm_op15_copy(ORDER_VECTOR, w); 
    for (i = 1; i <= o; ++i) {   
        res = mm_op15_word(w, g, n, 1, work);
        if (res < 0) goto done;
        res = mm_op15_order_find_in_Gx0(w, h);
        if (res >= 0x100) continue;
        if (res < 0) goto done;
        mm_op15_copy(w, w1);    
        res = mm_op15_order_find_in_Qx0(w1, h, work);
        if (res < 0) goto done;
        if (res < 0x100) {
            res += i << 8;
            goto done;
        } 
    }  
    res = 0;

  done:
    if (w) free(w);
    return res;
}




/**
  @brief Compute order of an element \f$g\f$ of the monster

  Let \f$g\f$ be the element of the monster group stored in the
  array ``g`` as a word of generators of the monster group of
  length ``n``.

  The function returns the order of \f$g\f$.
   
  Computation of the order is time consuming, and in some cases 
  we are interested in small orders only.
  Parameter ``o`` is an upper bound for the order. The function 
  may return 0 if the order is greater than ``o``.

  This function requires that an order vector has been stored by
  a previous call to function ``mm_op15_store_order_vector``.

  A negative return value indicates an error. 
*/
// %%EXPORT px
int32_t mm_op15_order(uint32_t *g, uint32_t n, uint32_t o)
{
    int32_t res, e;
    uint32_t h[10];

    res = mm_op15_order_Gx0(g, n, h, o);
    if (res <= 0) return res;
    e =  res >> 8;
    if ((res & 0xff) > 10) return ERR_QSTATE12_BUFFER_OVFL;
    res = xsp2co1_order_word(h, res & 0xff);
    if (res <= 0) return res ? res : ERR_QSTATE12_SCALAR_OVFL;
    return e * res;
}

