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

#include <NTL/vec_GF2E.h>
#include <NTL/mat_GF2.h>
#include "ffi.h"
#include "ffi_elt.h"
#include "ffi_vec.h"

using namespace std;


/**
 * \fn void ffi_vec_set(ffi_vec& o, const ffi_vec& v, unsigned int size)
 * \brief This function copies a vector over a finite field into another one
 *
 * \param[out] o Vector over a finite field
 * \param[in] v Vector over a finite field
 * \param[in] size Size of the vectors
 */
void ffi_vec_set(ffi_vec& o, const ffi_vec v, unsigned int size) {
  VectorCopy(o, v, size);
}

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

/**
 * \fn void ffi_vec_set_random_using_rng(ffi_vec& o, unsigned int size)
 * \brief This function sets a vector with random values using the NIST randombytes function
 *
 * \param[out] o Vector over a finite field
 * \param[in] size Size of the vector <b>v</b>
 */
void ffi_vec_set_random_using_rng(ffi_vec& o, unsigned int size) {
  for(unsigned int i = 0; i < size; i++)
  {
    ffi_elt_set_random_using_rng(o[i]);
	//ffi_elt_set_random_using_ntl(o[i]);
  }
}


/**
 * \fn void ffi_vec_set_random_full_rank_with_one_using_rng(ffi_vec& o, unsigned int size)
 * \brief This function sets a vector with random values using the NIST randombytes function. In addition, the vector returned by this function has full rank and contains one.
 *
 * \param[out] o Full rank vector over a finite field containing one
 * \param[in] size Size of the vector <b>v</b>
 */
void ffi_vec_set_random_full_rank_with_one_using_rng(ffi_vec& o, unsigned int size) {
  ffi_elt one;
  ffi_elt_set_one(one);
  o.SetLength(size);
  unsigned int rank = -1;
  while(rank != size) {
    ffi_vec_set_random_using_rng(o, size - 1);
    ffi_elt_set(o[size-1], one);
    rank = ffi_vec_get_rank(o, size);
  } 
}

/**
 * \fn void ffi_vec_set_random_full_rank_using_rng(ffi_vec& o, unsigned int size)
 * \brief This function sets a vector with random values using the NIST randombytes function. In addition, the vector returned by this function has full rank.
 *
 * \param[out] o Full rank vector over a finite field
 * \param[in] size Size of the vector <b>v</b>
 */
void ffi_vec_set_random_full_rank_using_rng(ffi_vec& o, unsigned int size) {
  unsigned int rank = -1;
  while(rank != size) {
    ffi_vec_set_random_using_rng(o, size);
    rank = ffi_vec_get_rank(o, size);
  }
  o.SetLength(size);
}


/**
 * \fn void ffi_vec_set_random_from_support_using_rng(ffi_vec& o, unsigned int size, const ffi_vec& support, unsigned int rank)
 * \brief This function sets a vector with random values using the NIST randombytes function. In addition, the support of the vector returned by this function is the one given in input.
 *
 * This function copies the support vector in rank random positions of <b>v</b>. Next, all the remaining coordinates of <b>v</b> are set using random linear combinations of the support coordinates.
 *
 * \param[out] o Vector of given support
 * \param[in] size Size of the vector <b>v</b>
 * \param[in] support Support of <b>v</b>
 * \param[in] rank Size of the vector support
 */
void ffi_vec_set_random_from_support_using_rng(ffi_vec& o, unsigned int size, const ffi_vec& support, unsigned int rank) {
  ffi_vec_set_zero(o, size);
  // Copy the support vector in rank random positions of o
  int random_position_size = 2 * rank;
  unsigned char* random_position = new unsigned char[random_position_size];
  randombytes(random_position, random_position_size);

  unsigned int i = 0;
  unsigned int j = 0;
  unsigned int position;
  while(i != rank) {
    position = random_position[j];
    // Perform rejection sampling and check that the position is not already taken
    if(position < size * (256 / size) && ffi_elt_is_zero(o[position % size]) == 1) {
      ffi_elt_set(o[position % size], support[i]);
      i++;
    }

    // Get more randomness if necessary
    j++;
    if(j % random_position_size == 0 && i != rank) {
      randombytes(random_position, random_position_size);
      j = 0;
    }
  }

  // Set all the remaining coordinates with random linear combinations of the support coordinates
  int random_lin_comb_size = rank * (size - rank);
  unsigned char* random_lin_comb = new unsigned char[random_lin_comb_size];
  randombytes(random_lin_comb, random_lin_comb_size);

  int k = 0;
  for(i = 0 ; i < size ; ++i) {
    if(ffi_elt_is_zero(o[i])) {
      for(j = 0 ; j < rank ; ++j) {
        if((random_lin_comb[k * rank + j] % FIELD_Q) == 1) {
          ffi_elt_add(o[i], support[j], o[i]);
        }
      }
      k++;
    }
  }

  o.SetLength(size);
  delete[] random_lin_comb;
  delete[] random_position;
}



/**
 * \fn void ffi_vec_add(ffi_vec& o, const ffi_vec v1, const ffi_vec v2, unsigned int size)
 * \brief This function adds two vectors over a finite field
 *
 * \param[out] o Sum of <b>v1</b> and <b>v2</b>
 * \param[in] v1 Vector over a finite field
 * \param[in] v2 Vector over a finite field
 * \param[in] size Size of the vectors
 */
void ffi_vec_add(ffi_vec &o, const ffi_vec v1, const ffi_vec v2, unsigned int size) {
  o.SetLength(size);
  add(o, v1, v2);
}



/**
 * \fn void ffi_vec_sub(ffi_vec& o, const ffi_vec v1, const ffi_vec v2, unsigned int size)
 * \brief This function substracts two vectors over a finite field
 *
 * \param[out] o Sub of <b>v1</b> and <b>v2</b>
 * \param[in] v1 Vector over a finite field
 * \param[in] v2 Vector over a finite field
 * \param[in] size Size of the vectors
 */
void ffi_vec_sub(ffi_vec &o, const ffi_vec v1, const ffi_vec v2, unsigned int size) {
  o.SetLength(size);
  sub(o, v1, v2);
}


/**
 * \fn void ffi_vec_inner_product(ffi_vec& o, const ffi_vec v1, const ffi_vec v2)
 * \brief This functions outputs innerproduct of two vectors over a finite field
 *
 * \param[out] o InnerProduct of <b>v1</b> and <b>v2</b>
 * \param[in] v1 Vector over a finite field
 * \param[in] v2 Vector over a finite field
 */
void ffi_vec_inner_product(ffi_elt &o, const ffi_vec v1, const ffi_vec v2) {
  InnerProduct(o, v1, v2);
}



/**
 * \fn unsigned int ffi_vec_get_rank(ffi_vec v, unsigned int size)
 * \brief This function computes the rank of a vector over a finite field namely the rank of the matrix induced by the vector over GF(2).
 *
 * \param[out] v Vector over a finite field
 * \param[in] size Size of the vector
 * \return The rank of the matrix
 */
unsigned int ffi_vec_get_rank(ffi_vec v, unsigned int size) {
  return ffi_vec_gauss(v, size);
}



/**
 * \fn unsigned int ffi_vec_gauss(ffi_vec o, unsigned int size)
 * \brief This function computes gaussian elimination on the matrix induced by a given vector over GF(2).
 *
 * \param[out] o Vector over a finite field
 * \param[in] size Size of the vector
 * \return The rank of the matrix
 */
unsigned int ffi_vec_gauss(ffi_vec o, unsigned int size) {
  mat_GF2 matrix;
  vec_vec_GF2 vectors;
  vectors.SetLength(size);
  vec_GF2 tmp;

  for(unsigned int i = 0 ; i < size ; ++i) {
    conv(tmp, rep(o[i]));
    tmp.SetLength(FIELD_M);
    vectors[i] = tmp;
  }

  conv(matrix, vectors);
  return gauss(matrix);
}

/**
 * \fn void ffi_vec_neg(ffi_vec o, ffi_vec v, unsigned int size)
 * \brief This function computes the additive inverse of a vector over a finite field
 *
 * \param[out] o Vector over a finite field
 * \param[in] v Vector over a finite field
 * \param[in] size Size of the vectors
 */
void ffi_vec_neg(ffi_vec& o, const ffi_vec v, unsigned int size) {
    o.SetLength(size);
    NTL::negate(o, v);
}


/**
 * \fn void ffi_vec_scalar_mul(ffi_vec& o, const ffi_vec v, const ffi_elt e, unsigned int size)
 * \brief This functions multiplies a vector over a finite field by a scalar
 *
 * \param[out] o Vector equal to \f$ e \times v \f$
 * \param[in] v Vector over a finite field
 * \param[in] e Finite field element
 * \param[in] size Size of the vectors
 */
void ffi_vec_scalar_mul(ffi_vec& o, const ffi_vec v, const ffi_elt e, unsigned int size) {
  o.SetLength(size);
  o = v * e;
}

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

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

/**
 * \fn void ffi_vec_set_random_using_shake256(ffi_vec& o, unsigned int size, unsigned char* seed, unsigned int seed_size)
 * \brief This function sets a vector with random values using shake256 function
 *
 * \param[out] o Vector over a finite field
 * \param[in] size Size of the vector <b>v</b>
 */
void ffi_vec_set_random_using_shake256(ffi_vec& o, unsigned int size, unsigned char* seed, unsigned int seed_size) {
  
  unsigned long long bytes = (FIELD_M+7)/8;//向量的每个元素使用的字节数
  unsigned char mask = (1 << FIELD_M % 8) - 1;//最后一个字节的MASK

  unsigned char* vec_random = new unsigned char[size*bytes];
  shake256(vec_random, size*bytes, seed, 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;

  //MASK掉每个向量的最后一个字节的高位
  for(unsigned int i=0; i<size; i++)
  {
    vec_random[ (i+1) * bytes - 1] &= mask;
    o[i]._GF2E__rep.xrep.SetLength(wl);
  }

#ifdef VERBOSE
  printf("\nRandom bytes");
  for (int i = 0; i < size; i++)
  {
	  printf("\nV[%02d]=", i);
	  for (int j = 0; j < bytes; j++)
		  printf("%02X ", vec_random[i * bytes + j]);
	  printf("\n");
  }
#endif

  for(unsigned int i=0; i<size; i++)
  {
  		unsigned char *p = vec_random+ i * bytes;
		long byte_left = bytes;
		for(long l = 0; l < wl; l++)
        {
            res = 0;
            for(long k = 0; k < bytes_of_long && byte_left>0; k++)
            {
                res |= ((*p)<<(k*8));
				byte_left--;
				p++;
            }
            o[i]._GF2E__rep.xrep[l] = res;
        }
        
		o[i]._GF2E__rep.normalize();
  }

#ifdef VERBOSE
  printf("\nvector o");
  for (long i = 0; i < size; i++) {
	  printf("\no[%02d] = ", i);
	  GF2X::HexOutput = 1;
	  ffi_elt_print(o.at(i));
  }

  printf("\no saved ");
  for (long i = 0; i < size; i++) {
	  printf("\no[%02d] = ", i);
	  for (int j = 0; j < o.at(i)._GF2E__rep.xrep.length(); j++) {
		  printf("%08X ", *(o.at(i)._GF2E__rep.xrep.elts() + j));
	  }
  }
#endif
  delete[] vec_random;
}

/**
 * \fn void ffi_vec_set_random_full_rank_using_shake256(ffi_vec& o, unsigned int size, unsigned char* seed, unsigned int seed_size)
 * \brief This function sets a vector with random values using shake256 function.
 * In addition, the vector returned by this function has full rank.
 *
 * \param[out] o Full rank vector over a finite field
 * \param[in] size Size of the vector <b>v</b>
 */
void ffi_vec_set_random_full_rank_using_shake256(ffi_vec& o, 
	unsigned int size,  //53
	unsigned char* seed, 
	unsigned int seed_size) //32
{
	unsigned int rank = -1;
	o.SetLength(size);
	unsigned char* update_seed = new unsigned char[seed_size];

  	ffi_vec_set_random_using_shake256(o, size, seed, seed_size);
	rank = ffi_vec_get_rank(o, size);
	while(rank != size)
	{
		shake256(update_seed,seed_size,seed,seed_size);
		memcpy(seed, update_seed, seed_size);
		ffi_vec_set_random_using_shake256(o, size, update_seed, seed_size);
		rank = ffi_vec_get_rank(o, size);
	}
}




/** 
 * \fn void ffi_vec_set_random_from_support_using_seedexpander(ffi_vec& o, unsigned int size, const ffi_vec& support, unsigned int rank, AES_XOF_struct* ctx)
 * \brief This function sets a vector with random values using the NIST seed expander. In addition, the support of the vector returned by this function is the one given in input.
 *
 * This function copies the support vector in rank random positions of <b>v</b>. Next, all the remaining coordinates of <b>v</b> are set using random linear combinations of the support coordinates.
 *
 * \param[out] o Vector of given support
 * \param[in] size Size of the vector <b>v</b>
 * \param[in] support Support of <b>v</b>
 * \param[in] rank Size of the vector support
 * \param[in] ctx NIST seed expander
 */
void ffi_vec_set_random_from_support_using_seedexpander(ffi_vec& o, unsigned int size, const ffi_vec& support, unsigned int rank, AES_XOF_struct* ctx) {
  ffi_vec_set_zero(o, size);

  // Copy the support vector in rank random positions of o
  int random_position_size = 2 * rank;
  unsigned char* random_position = new unsigned char[random_position_size];
  seedexpander(ctx, random_position, random_position_size);

  unsigned int i = 0;
  unsigned int j = 0;
  unsigned int position;
  while(i != rank) {
    position = random_position[j];
    // Perform rejection sampling and check that the position is not already taken
    if(position < size * (256 / size) && ffi_elt_is_zero(o[position % size]) == 1) {
	  ffi_elt_set(o[position % size], support[i]);
      i++;
    }

    // Get more randomness if necessary
    j++;
    if(j % random_position_size == 0 && i != rank) {
      seedexpander(ctx, random_position, random_position_size);
      j = 0;
    }
  }

  // Set all the remaining coordinates with random linear combinations of the support coordinates
  int random_lin_comb_size = rank * (size - rank);
  unsigned char* random_lin_comb = new unsigned char[random_lin_comb_size];
  seedexpander(ctx, random_lin_comb, random_lin_comb_size);

  int k = 0;
  for(i = 0 ; i < size ; ++i) {
    if(ffi_elt_is_zero(o[i])) {
      for(j = 0 ; j < rank ; ++j) {
        if((random_lin_comb[k * rank + j] % FIELD_Q) == 1) {
          ffi_elt_add(o[i], support[j], o[i]);
        }
      }
      k++;
    }
  }

  o.SetLength(size);
}

/** 
 * \fn void ffi_vec_set_random_full_rank_with_one_using_seedexpander(ffi_vec& o, unsigned int size, AES_XOF_struct* ctx)
 * \brief This function sets a vector with random values using the NIST seed expander.
 * In addition, the vector returned by this function has full rank and contains one.
 *
 * \param[out] o Full rank vector over a finite field containing one
 * \param[in] size Size of the vector <b>v</b>
 * \param[in] ctx NIST seed expander
 */
void ffi_vec_set_random_full_rank_with_one_using_seedexpander(ffi_vec& o, unsigned int size, AES_XOF_struct* ctx) {
  ffi_elt one;
  ffi_elt_set_one(one);
  o.SetLength(size);
  ffi_elt_set(o[size-1], one);
  unsigned int rank = -1;
  while(rank != size) {
    ffi_vec_set_random_using_seedexpander(o, size - 1, ctx);
    rank = ffi_vec_get_rank(o, size);
  }
}

/** 
 * \fn void ffi_vec_set_random_using_seedexpander(ffi_vec& o, unsigned int size, AES_XOF_struct* ctx)
 * \brief This function sets a vector with random values using the NIST seed expander
 *
 * \param[out] o Vector over a finite field
 * \param[in] size Size of the vector <b>v</b>
 * \param[in] ctx NIST seed expander
 */
void ffi_vec_set_random_using_seedexpander(ffi_vec& o, unsigned int size, AES_XOF_struct* ctx) {
  for(unsigned int i = 0 ; i < size ; ++i) {
    ffi_elt_set_random_using_seedexpander(o[i], ctx);
  }
}

/* This functions tests if two vectors over a finite field are equal*/
int ffi_vec_is_equal_to(const ffi_vec& o1, const ffi_vec& o2, long n)
{
	ffi_vec o;
	o.SetLength(n);
	sub(o, o1, o2);
	return IsZero(o);
}

/** 
 * \fn void ffi_vec_mul(ffi_field f, ffi_vec o, const ffi_vec v1, const ffi_vec v2, unsigned int size)
 * \brief Multiply two vector over a finite field
 *
 * Vector multiplication is defined as polynomial multiplication over \f$ GF(2^d)/(X^{size} - 1) \f$.
 *
 * \param[in] f Finite field
 * \param[out] o Product of <b>v1</b> and <b>v2</b>
 * \param[in] v1 Vector over a finite field
 * \param[in] v2 Vector over a finite field
 * \param[in] size Size of the vectors
 */
void ffi_vec_mul(ffi_vec o, const ffi_vec v1, const ffi_vec v2, unsigned int size) {
	o.SetLength(size);
	ffi_elt sum, tmp;
			
	for(unsigned int i = 0; i < size; i++)
	{
		ffi_elt_set_zero(sum);
		for(unsigned int j = 0; j < i+1; j++)
		{	
			ffi_elt_mul(tmp, v1[j], v2[i-j]);
			ffi_elt_add(sum, tmp, sum);
		}
		for(unsigned int j = i+1; j<size; j++)
		{
			ffi_elt_mul(tmp, v1[j], v2[size+i-j]);
			ffi_elt_add(sum, tmp, sum);
		}
		ffi_elt_set(o[i], sum);
	}
}