/**
 * \file ffi_mat.c
 * \brief Implementation of ffi_mat.h using NTL library
 */

#include <NTL/mat_GF2E.h>
#include "fips202.h"
#include "ffi.h"
#include "ffi_elt.h"
#include "ffi_vec.h"
#include "ffi_mat.h"

using namespace std;


/**
 * \fn void ffi_mat_set(ffi_mat& o, const ffi_mat v, unsigned int n, unsigned int m)
 * \brief This function copies a matrix over a finite field into another one
 *
 * \param[out] o Matrix over a finite field
 * \param[in] v Matrix over a finite field
 * \param[in] n Size of the row
 * \param[in] m Size of the column
 */
void ffi_mat_set(ffi_mat& o, const ffi_mat v, unsigned int n, unsigned int m) {
  o.SetDims(n,m);
  for(unsigned int i = 0; i < n; i++)
  {
      for(unsigned int j = 0; j < m; j++){
        ffi_elt_set(o[i][j], v[i][j]);}
  }
}

/**
 * \fn void ffi_mat_set_zero(ffi_mat& v, unsigned int n, unsigned int m)
 * \brief This function sets a Matrix over a finite field to zero
 *
 * \param[out] o Matrix over a finite field
 * \param[in] size Size of the vector <b>v</b>
 */
void ffi_mat_set_zero(ffi_mat& o, unsigned int n, unsigned int m) {
    o.SetDims(n,m);
    for(unsigned int i = 0 ; i < n ; i++) {
        ffi_vec_set_zero(o[i], m);
    }
}


/**
 * \fn void ffi_mat_add(ffi_mat& o, const ffi_mat m1, const ffi_mat m2)
 * \brief This function outputs the sum of two matrixs over a finite field
 *
 * \param[out] o Sum of <b>m1</b> and <b>m2</b>
 * \param[in] m1 Matrix over a finite field
 * \param[in] m2 Matrix over a finite field
 */
void ffi_mat_add(ffi_mat& o, const ffi_mat m1, const ffi_mat m2) {
  add(o, m1, m2);
}



/**
 * \fn void ffi_mat_sub(ffi_mat& o, const ffi_mat m1, const ffi_mat m2)
 * \brief This function substracts two amtrixs over a finite field
 *
 * \param[out] o Sub of <b>m1</b> and <b>m2</b>
 * \param[in] m1 Matrix over a finite field
 * \param[in] m2 Matrix over a finite field
 */
void ffi_mat_sub(ffi_mat& o, const ffi_mat m1, const ffi_mat m2) {
  sub(o, m1, m2);
}


/**
 * \fn void ffi_mat_mul(ffi_mat& o, const ffi_mat m1, const ffi_mat m2)
 * \brief This functions outputs multiplication of two matrixs over a finite field
 *
 * \param[out] o Mul of <b>m1</b> and <b>m2</b>
 * \param[in] m1 Matrix over a finite field
 * \param[in] m2 Matrix over a finite field
 */
void ffi_mat_mul(ffi_mat& o, const ffi_mat m1, const ffi_mat m2) {
  mul(o, m1, m2);
}

/**
 * \fn void ffi_mat_mul(ffi_vec& o, const ffi_mat m1, const ffi_vec v2)
 * \brief This functions outputs multiplication of a matrix and a vector over a finite field
 *
 * \param[out] o Mul of <b>m1</b> and <b>v2</b>
 * \param[in] m1 Matrix over a finite field
 * \param[in] v2 Vector over a finite field
 */
void ffi_mat_mul(ffi_vec& o, const ffi_mat m1, const ffi_vec v2) {
  mul(o, m1, v2);
}

/**
 * \fn void ffi_mat_mul(ffi_vec& o, const ffi_vec& v1, const ffi_mat& m2)
 * \brief This functions outputs multiplication of a vector and a matrix over a finite field
 *
 * \param[out] o Mul of <b>v1</b> and <b>m2</b>
 * \param[in] v1 Vector over a finite field
 * \param[in] m2 Matrix over a finite field
 */
void ffi_mat_mul(ffi_vec& o, const ffi_vec v1, const ffi_mat m2) {
  mul(o, v1, m2);
}


/**
 * \fn void ffi_mat_negate(ffi_mat& o, const ffi_mat m)
 * \brief This function negates a matrix over a finite field
 *
 * \param[out] o Nega of <b>m</b>
 * \param[in] m Matrix over a finite field
 */
void ffi_mat_negate(ffi_mat& o, const ffi_mat m) {
  NTL::negate(o, m);
}


/**
 * \fn void ffi_mat_transpose(ffi_mat& o, const ffi_mat m)
 * \brief This function transposes a matrix over a finite field
 *
 * \param[out] o Trans of <b>m</b>
 * \param[in] m Matrix over a finite field
 */
void ffi_mat_transpose(ffi_mat& o, const ffi_mat m) {
  transpose(o, m);
}

/**
 * \fn unsigned int ffi_mat_is_zero(ffi_mat& o)
 * \brief This function test if a matrix is the zero matrix over a finite field
 *
 * \param[out] 1 if the matrix is zero matrix
 * \param[in] m Matrix over a finite field
 */
unsigned int ffi_mat_is_zero(ffi_mat& o) {
  return IsZero(o);
}

/**
 * \fn void ffi_mat_set_random_using_rng(ffi_mat& o, unsigned int n, unsigned int m)
 * \brief This function outputs a matrix with random values using the NIST randombytes function over a finite field
 *
 * \param[out] o Trans of <b>m</b>
 * \param[in] n Size of the row
 * \param[in] m Size of the column
 */
void ffi_mat_set_random_using_rng(ffi_mat& o, long n, long m) {
  o.SetDims(n,m);
  for( int i = 0; i < n; i++)
        for( int j=0; j < m; j++)
        {
            ffi_elt_set_random_using_rng(o[i][j]);
        }
}


/**
 * \fn void ffi_mat_set_random_from_support_using_rng(ffi_mat& o, unsigned int size, const ffi_vec& support, unsigned int rank)
 * \brief This function sets a matrix with random values using the NIST randombytes function. In addition, the support of the vector of each row returned by this function is the one given in input.
 *
 * This function copies the support vector in rank random positions of <b>v</b> in any row. Next, all the remaining coordinates of <b>v</b> are set using random linear combinations of the support coordinates.
 *
 * \param[out] o Matrix of given support
 * \param[in] n Size of the row
 * \param[in] m Size of the column
 * \param[in] support Support of <b>v</b>
 * \param[in] rank Size of the vector support
 */
void ffi_mat_set_random_from_support_using_rng(ffi_mat& o, long n, long m, const ffi_vec& support, unsigned int rank) {
    o.SetDims(n,m);
    for( int i = 0; i < n; i++)
        ffi_vec_set_random_from_support_using_rng(o[i], m, support, rank);
}


/**
 * \fn void ffi_mat_set_random_using_shake256(ffi_mat& o, unsigned int n, unsigned int m, unsigned char* seed, unsigned int size)
 * \brief This function outputs a matrix with random values using the shake256 from a 256-bit seed over a finite field
 *
 * \param[out] o Trans of <b>m</b>
 * \param[in] n Size of the row
 * \param[in] m Size of the column
 * \param[in] seed Seed to be used for Shake256
 */

void ffi_mat_set_random_using_shake256(ffi_mat& o, long n, long m, unsigned char* seed, unsigned int size) {
  o.SetDims(n,m);
  unsigned long long bytes = (FIELD_M % 8 == 0) ? FIELD_M / 8 : FIELD_M / 8 + 1;
  unsigned char mask = (1 << FIELD_M % 8) - 1;

  unsigned char* mat_random = new unsigned char[n*m*bytes];
  shake256(mat_random, n*m*bytes, seed, size);

  long wl = (FIELD_M + NTL_BITS_PER_LONG - 1) / NTL_BITS_PER_LONG;
  long bytes_of_long = NTL_BITS_PER_LONG / (8);
  unsigned long res = 0;

  for( int i=0; i<n; i++)
    for( int j=1; j<=m; j++)
      {
        mat_random[i * m * bytes + j * bytes - 1] &= mask;
        o[i][j-1]._GF2E__rep.xrep.SetLength(wl);
      }

  for( int i=0; i<n; i++)
  {
    for( int j=1; j<=m; j++)
    {
        for(long l = 0; l < wl-1; l++)
        {
            res = 0;
            for(long k = i * m * bytes + (j-1) * bytes + l * bytes_of_long; k < i * m * bytes + (j-1) * bytes + l * bytes_of_long + bytes_of_long; k++)
            {
                res = (res << 8) | mat_random[k];
            }
            o[i][j-1]._GF2E__rep.xrep[l] = res;
        }

        if(FIELD_M > 0){
            long pos_bytes = bytes % bytes_of_long;
            if(pos_bytes == 0) pos_bytes = bytes_of_long;
            res = 0;
            for(long k = i * m * bytes + (j-1) * bytes + (wl-1) * bytes_of_long; k < i * m * bytes + (j-1) * bytes + (wl-1) * bytes_of_long + pos_bytes; k++)
            {
                res = (res << 8) | mat_random[k];
            }
            o[i][j-1]._GF2E__rep.xrep[wl-1] = res;
        }
        o[i][j-1]._GF2E__rep.normalize();
    }
  }
  delete[] mat_random;
}

/* This function only copies the bits of mat m containing information into str */
void ffi_mat_to_string(unsigned char* str, const ffi_mat& o, unsigned int n, unsigned int m)
{
    for(unsigned int i = 0; i < n; i++)
    {
        for(unsigned int j = 0; j < m; j++)
        {
            ffi_elt_to_string(str + (i * m + j) * FIELD_ELT_BYTES, o[i][j]);
        }
    }
}

/* This function only copies str into the bits of mat m containing information */
void ffi_mat_from_string(const unsigned char* str, ffi_mat& o, unsigned int n, unsigned int m)
{
    for(unsigned int i = 0; i < n; i++)
    {
        for(unsigned int j = 0; j < m; j++)
        {
            ffi_elt_from_string(o[i][j], str + (i * m + j) * FIELD_ELT_BYTES);
        }
    }
}

/* This functions tests if two matrics over a finite field are equal*/
int ffi_mat_is_equal_to(const ffi_mat& m1, const ffi_mat& m2, long n, long m)
{
	ffi_mat o;
	o.SetDims(n, m);
	sub(o, m1, m2);
	return IsZero(o);
}