////////////////////////////////////////////////////
//////VIRTUAL//METHODS//OF//THE//BASE//CLASS////////
////////////////////////////////////////////////////

////////////////
//CONSTRUCTORS//
////////////////

/*!
Allocate the space needed to fill a vector containing all the round keys.
*/
template <unsigned nb_msg, unsigned nb_key, unsigned nb_sbox, unsigned nround>
inline
Bunny<nb_msg, nb_key, nb_sbox,nround>::Bunny(){
  rk.resize(nround+1) ; // allocates memory for the round keys
}


/////////////////////////////////////////////////
////////////////MISC//FUNCTIONS//////////////////
/////////////////////////////////////////////////

//!Print to the terminal the current parameters of the block cipher.
template <unsigned nb_msg, unsigned nb_key, unsigned nb_sbox, unsigned nround>
inline
void
Bunny<nb_msg,nb_key,nb_sbox,nround>::printParameter() {
    std::cout << "-------------------------------\n" ;
    std::cout << "| BUNNY's PARAMETERS are:  " << setw (5) <<            "|\n" ;
    std::cout << "|       S-box size:      "   << setw (5) << nb_sbox << "|\n" ;
    std::cout << "|       Message size:    "   << setw (5) << nb_msg  << "|\n" ;
    std::cout << "|       Key size:        "   << setw (5) << nb_key  << "|\n" ;
    std::cout << "|       Number of Rounds:"   << setw (5) << nround  << "|\n" ;
    std::cout << "-------------------------------\n" ;
}

//!Extracts nb_sbox from a msgType x, i.e. extracts the block number nblk (block 0 is the rightmost) from x.
/*!
Extracts nb_sbox bits in position [nblk*nb_sbox..nblk*nb_sbox + nb_sbox-1] from a string of type msgType and insert them in a string of type sboxType.

Exits from program if the position pos is negative or greater then the size of msgType minus the size of sboxType.
*/
template <unsigned nb_msg, unsigned nb_key, unsigned nb_sbox, unsigned nround>
inline
typename Bunny<nb_msg,nb_key,nb_sbox,nround>::sboxType
Bunny<nb_msg,nb_key,nb_sbox,nround>::extractBlock( unsigned nblk, msgType x ) {
  sboxType y ;
  for ( unsigned i = 0 ; i < nb_sbox ; ++i) y[i] = x[i+nblk*nb_sbox] ;
  return y ;
}

//! Extracts from word of type wordType nb_sbox bits and return them as an sboxType.
template <unsigned nb_msg, unsigned nb_key, unsigned nb_sbox, unsigned nround>
inline
typename Bunny<nb_msg,nb_key,nb_sbox,nround>::sboxType
Bunny<nb_msg,nb_key,nb_sbox,nround>::extractFromWordToSboxType( unsigned pos, wordType x ) {
  sboxType y ;
  ASSERT( pos <= (x.size() - y.size()) && pos >= 0,
    "Error: trying to extract from a position which is not allowed!" ) ;
  pos = x.size() - pos - y.size() ; // this is because x[0] refers to the rightmost bit
  // std::copy( x . begin() + pos, x . begin() + pos + y . size(), y.begin() ) ;
  for ( unsigned i = 0 ; i < y.size() ; ++i) y[i] = x[i+pos] ;
  return y ;
}

//! Copies the bitset x in m in the block nblk of m (to copy a string fitting the sbox size into a message).
/*!
- INPUT: a message m of type msgType, the position pos where to start the copy, the part x of the message to be copied of type sboxType.

- OUTPUT: the new copy of m.
*/
template <unsigned nb_msg, unsigned nb_key, unsigned nb_sbox, unsigned nround>
inline
typename Bunny<nb_msg,nb_key,nb_sbox,nround>::msgType
Bunny<nb_msg,nb_key,nb_sbox,nround>::insertBlock( msgType m, unsigned nblk, sboxType x ) {
  for (unsigned i = 0 ; i < nb_sbox ; ++i ) m[i+nblk*nb_sbox] = x[i] ;
  return m;
}

//!Copies the bitset x in m at position pos (to copy a word into a key).
/*!
- INPUT: a key m of type keyType, the position pos where to start the copy, the part x of the message to be copied of type wordType.

- OUTPUT: the new copy of m.
*/
template <unsigned nb_msg, unsigned nb_key, unsigned nb_sbox, unsigned nround>
inline
typename Bunny<nb_msg,nb_key,nb_sbox,nround>::keyType
Bunny<nb_msg,nb_key,nb_sbox,nround>::copyIntoKey(keyType m, unsigned pos, wordType x){
  pos = m.size() - pos - x.size() ; // this is because x[0] refers to the rightmost bit
  for (unsigned i = 0 ; i < x.size() ; ++i ) m[i+pos] = x[i] ;
  return m;
}

//! Copies the bitset x in m at position pos (to copy a word into a message).
/*!
- INPUT: a message m of type msgType, the position pos where to start the copy, the part x of the message to be copied of type wordType.

- OUTPUT: the new copy of m.
*/

template <unsigned nb_msg, unsigned nb_key, unsigned nb_sbox, unsigned nround>
inline
typename Bunny<nb_msg,nb_key,nb_sbox,nround>::wordType
Bunny<nb_msg,nb_key,nb_sbox,nround>::copyIntoWord(wordType m, unsigned pos, sboxType x){
  pos = m.size() - pos - x.size() ; // this is because x[0] refers to the rightmost bit
  for (unsigned i = 0 ; i < x.size() ; ++i ) m[i+pos] = x[i] ;
  return m;
}

//! Extract a word of 32 bits from a keyType element.
/*!
- INPUT: a key x, and the position pos which indicates where the extraction has to be made.

- OUTPUT: a string y of 32 bits.
*/
template <unsigned nb_msg, unsigned nb_key, unsigned nb_sbox, unsigned nround>
inline
typename Bunny<nb_msg,nb_key,nb_sbox,nround>::wordType
Bunny<nb_msg,nb_key,nb_sbox,nround>::extractWord( unsigned pos, keyType x ) {
  wordType y ;
  ASSERT( pos <= (x.size() - y.size()) && pos >= 0,
    "Error: trying to extract from a position which is not allowed!" ) ;
  pos = x.size() - pos - y.size() ; // this is because x[0] refers to the rightmost bit
   // std::copy( x . begin() + pos, x . begin() + pos + y . size(), y.begin() ) ;
  for ( unsigned i = 0 ; i < y.size() ; ++i) y[i] = x[i+pos] ;
  return y ;
}


//////////////////////////////////
///////ENCODING//FUNCTIONS////////
//////////////////////////////////

//!Encoding function for Bunny block cipher.
/*!
- INPUT: a message m of type msgType (a bitset of dimension N) and a key k of type keyType (a bitset of dimension M).

- OUTPUT: an (encrypted) message c of the same type as m.
*/
template <unsigned nb_msg, unsigned nb_key, unsigned nb_sbox, unsigned nround>
inline
typename Bunny<nb_msg,nb_key,nb_sbox,nround>::msgType
Bunny<nb_msg,nb_key,nb_sbox,nround>::encode( msgType m, keyType k ) {
  msgType c ;
  c = m ;
  keySchedule(k) ;
  //Round 0, a-tipical
  c = addRoundKey(c,rk[0]) ;
  //cout << "state at round: 0 --> " << bitsetToHex(c) << endl ;
  //Tipical rounds
  for (unsigned i = 1 ; i <= nround ; ++i){
    c = sBox(c) ;
    //cout << "state after sBox: --> " << bitsetToHex(c) << endl ;
    c = mixingLayer(c) ;
    //cout << "state after mixL: --> " << bitsetToHex(c) << endl ;
    c = addRoundKey(c,rk[i]) ;
    //cout << "state at round: " << i <<  " --> " << bitsetToHex(c) << endl ;
  }
  return c ;
}


//!Decoding function for Bunny block cipher.
/*!
- INPUT: a message m of type msgType (a bitset of dimension N) and a key k of type keyType (a bitset of dimension M).

- OUTPUT: an (decrypted) message c of the same type as m.
*/
template <unsigned nb_msg, unsigned nb_key, unsigned nb_sbox, unsigned nround>
inline
typename Bunny<nb_msg,nb_key,nb_sbox,nround>::msgType
Bunny<nb_msg,nb_key,nb_sbox,nround>::decode (msgType m, keyType k) {
  msgType c ;
  c = m ;
  //keyschedule
  keySchedule(k) ;
  //Tipical rounds
  for (unsigned i = nround ; i > 0 ; --i){
    c = addRoundKey(c,rk[i]) ;
    //cout <<   "state after add round: " << i <<  " --> " << bitsetToHex(c) << endl ;
    c = mixingLayerInverse(c) ;
    //cout << "state after mixLInv: --> " << bitsetToHex(c) << endl ;
    c = sBoxInverse(c) ;
    //cout << "state after sBoxInv: --> " << bitsetToHex(c) << endl ;
  }
  //Round 0, a-tipical
  c = addRoundKey(c,rk[0]) ;
  return c ;
}

//////////////////////////////////////////////
///////////////KEY-SCHEDULE///////////////////
//////////////////////////////////////////////

//!Key-Schedule STEP - Bunny's style.
/*!
- INPUT: a master key k of type keyType.

- OUTPUT: return a pointer to a vector of msgType (this elements are the round key, which must be the same length/type as the message).
*/
template <unsigned nb_msg, unsigned nb_key, unsigned nb_sbox, unsigned nround>
inline
void Bunny<nb_msg,nb_key,nb_sbox,nround>::keySchedule(keyType k) {
  //number of word generated
  unsigned nword = 80 ;//(nround+10) * 4 ;//to have some more bitsj
  //dimension of the blocks that fill the round keys
  //i.e.: nrow round keys at a time are filled
  unsigned ncol = nb_msg/nb_sbox ;
  unsigned nrow = nb_msg/nb_sbox + 1 ;
  vector<sboxType> w ;

  w.resize(nword) ; // initialize a vector of nword elements of type sboxType, set to 00...0

  //CREATE W, a vector of 80 words
  //put the key bits in the first 4 words
  for (unsigned i = 0 ; i < (nb_msg/nb_sbox) ; ++i){
    w[i] = extractBlock(i,k) ;
  }
  w[4] = sbox(0,w[0]) ^ w[1];
  w[5] = sbox(1,w[1]) ^ w[2];
  w[6] = sbox(2,w[2]) ^ w[3];
  w[7] = sbox(3,w[3]) ^ w[0];

  for (unsigned i = 8; i < w.size() ; ++i){
    // se i = 0 mod 8 => w[i] = w[i-8] ^ s2(RB(w[i-1])) + 101010
    if (i % 8 == 0) w[i] = w[i-8] ^ sbox(1,(w[i-1]<<1)) ^ sboxType (string("101010")) ;
    // se i = 4 mod 8 => w[i] = w[i-8] ^ s3(w[i-1])
    else if (i % 8 == 4) w[i] = w[i-8] ^ sbox(2,w[i-1]) ;
    // se i != 0 mod 4 w[i] = w[i-8] ^ w[i-1]
    else w[i] = w[i-8] ^ w[i-1] ;
  }

  //CREATE the ROUND KEYS
  //0,5,10,15
  //1,6,11,16
  //2,7,12,17
  //3,8,13,18
  //4,9,14,19
  //..
  //20,25,30,35...
  unsigned pos = 0 ;
  for (unsigned i = 0 ; i <= nround ; ++i){
    if ( (i % nrow) == 0 ) pos = i * ncol ;
    for (unsigned j = 0 ; j < ncol ; ++j){
      //rk[i] = Bunny<nb_msg,nb_key,nb_sbox,nround>::copy(rk[i],j*nb_sbox,w[pos+j*nrow]) ;
    }
    ++pos ;
  }

  //Print W
  /*cout << endl ;
  cout << "k = " << k << endl ;
  cout << "W = " << endl ;
  for (unsigned i = 0; i < w.size() ; ++i){
    if (i % 4 == 3) cout << " " << w[i] << endl ;
    else cout << " " << w[i] ;
  }*/
return ;
}

///////////////////////////////
//////BUNNY Add Round Key//////
///////////////////////////////

//!Add round key STEP.
/*!
Sum with a round key k, which must be the same length of the message m.

- INPUT: a message m of type msgType, and a round key k of type msgType (the type must be the same as the message, otherwise they can't be added together)

- OUTPUT: the exor of m and k
*/
template <unsigned nb_msg, unsigned nb_key, unsigned nb_sbox, unsigned nround>
inline
typename Bunny<nb_msg,nb_key,nb_sbox,nround>::msgType
Bunny<nb_msg,nb_key,nb_sbox,nround>::addRoundKey (msgType m, msgType k){
  return m ^ k;
}

////////////////////////////////
//////BUNNY Nonlinear Step//////
////////////////////////////////

//!Bunny S-box STEP.
/*!
This function receives a message and applies the sboxes as many times as needed (using four different sbox tables):

  m = (m_1, m_2, ..., m_3)  ===>  (sbox_1(m_1), sbox_2(m_2), ..., sbox_r(m_r))

- INPUT: a message m of type msgType.

- OUTPUT: the message m elaborated by the sbox.
*/
template <unsigned nb_msg, unsigned nb_key, unsigned nb_sbox, unsigned nround>
inline
typename Bunny<nb_msg,nb_key,nb_sbox,nround>::msgType
Bunny<nb_msg,nb_key,nb_sbox,nround>::sBox (msgType m){
  msgType  c = m ;
  unsigned n = nb_msg / nb_sbox ;
  for (unsigned i = 0 ; i < n ; ++i){
    sboxType s ;
    s = extractBlock(i,m) ; // extract nb_sbox bits starting from left to right
    s = sbox(i,s) ; // elaborate the nb_sbox bits extracted
    c = insertBlock(c,i,s) ; // put nb_bit bits in the position i*nb_sbox of the message c
  }
  return c;
}

//!Bunny S-box Inverse STEP.
template <unsigned nb_msg, unsigned nb_key, unsigned nb_sbox, unsigned nround>
inline
typename Bunny<nb_msg,nb_key,nb_sbox,nround>::msgType
Bunny<nb_msg,nb_key,nb_sbox,nround>::sBoxInverse (msgType m){
  msgType  c = m ;
  unsigned n = nb_msg / nb_sbox ;
  for (unsigned i = 0 ; i < n ; ++i){
    sboxType s ;
    s = extractBlock(i,m) ; // extract nb_sbox bits starting from left to right
    s = sboxInverse(i,s) ; // elaborate the nb_sbox bits extracted
    c = insertBlock(c,i,s) ; // put nb_bit bits in the position i*nb_sbox of the message c
  }
  return c;
}

