uint64_t binary_to_decimal(std::array<uint64_t, 64> inp)
{

	uint64_t output = 0;		
	for(size_t j = 0; j < 64; ++j)
	{
		output += (1ULL << j) * inp[j];
	}

	return output;
}

struct shareconversion
{
  int64_t PM, PM_recv, rb, FCWshare_reconstruction;
};

/**
 * @brief This function is used to convert the XOR-ed shared flag bits into additive shares of the same
 * 
 * @param sockets0 
 * @param sockets1 
 * @param socket_no 
 */
void convert_sharesP2(size_t db_nitems, __m128i** output0, int8_t ** flags0,  __m128i** output1, int8_t ** flags1, 
                    int64_t ** leaves0, int64_t ** leafbits0, int64_t ** leaves1, int64_t ** leafbits1,
                    
                     std::vector<socket_t>& sockets0, std::vector<socket_t>& sockets1, size_t i, size_t socket_no = 0)
{ 
 du_attalah_P2(sockets0, sockets1, socket_no); 

 shareconversion shareconversionP0, shareconversionP1;
 boost::asio::read(sockets0[i], boost::asio::buffer(&shareconversionP0, sizeof(shareconversion)));
 boost::asio::read(sockets1[i], boost::asio::buffer(&shareconversionP1, sizeof(shareconversion)));
 
 for(size_t j = 0; j < db_nitems; ++j)
	{ 
   output1[i][j] =  -output1[i][j];
   flags1[i][j]  =  -flags1[i][j]; 	 
	}

 int64_t * flags0_  = (int64_t *)std::aligned_alloc(sizeof(node_t), db_nitems * sizeof(int64_t)); 
 int64_t * flags1_  = (int64_t *)std::aligned_alloc(sizeof(node_t), db_nitems * sizeof(int64_t));
 
 for(size_t j = 0; j < db_nitems; ++j)
	{
 
		  leaves0[i][j]  = output0[i][j][0];
    leaves1[i][j]  = output1[i][j][0];

    flags0_[j] = (flags0[i][j] * shareconversionP0.PM) + (flags0[i][j] * shareconversionP0.PM_recv) + (flags0[i][j] * shareconversionP0.rb); 
		  flags0_[j] += output0[i][j][1]; 

    flags1_[j] = (flags1[i][j] * shareconversionP1.PM) + (flags1[i][j] * shareconversionP1.PM_recv) + (flags1[i][j] * shareconversionP1.rb); 
		  flags1_[j] += output1[i][j][1]; 

	 
		  flags0_[j] -= (flags0[i][j] * shareconversionP0.FCWshare_reconstruction);		
		  flags1_[j] -= (flags1[i][j] * shareconversionP1.FCWshare_reconstruction);
		 
	 	 flags0[i][j] = flags0_[j];
		  flags1[i][j] = flags1_[j];
	
    if(flags0[i][j] == 128 || flags0[i][j] == -128) flags0[i][j] = 0;
		  leafbits0[i][j] = flags0[i][j]; 
    if(flags1[i][j] == 128 || flags1[i][j] == -128) flags1[i][j] = 0;
		  leafbits1[i][j] = flags1[i][j]; 
	 }

    #ifdef VERBOSE
        for(size_t j = 0; j < db_nitems; ++j)
        {		
            int64_t leafbit_reconstruction = leafbits0[i][j] + leafbits1[i][j];
            if(leafbit_reconstruction != 0) std::cout << std::dec << j << ":-> " << leafbit_reconstruction << std::endl;   
        }
    #endif
      
      P2_write_evalfull_outs_into_a_file(false, 0, db_nitems,  flags0[0], 	leaves0[0]);
      P2_write_evalfull_outs_into_a_file(true,  0, db_nitems,  flags1[0], 	leaves1[0]);
} 

void  P2_xor_to_additive(tcp::socket& s0, tcp::socket& s1, size_t socket_no)
{
   uint64_t x0, x1, y0, y1, gamma0, gamma1, alpha;

   arc4random_buf(&x0, sizeof(uint64_t));
   arc4random_buf(&x1, sizeof(uint64_t));
   arc4random_buf(&y0, sizeof(uint64_t));
   arc4random_buf(&y1, sizeof(uint64_t));
   arc4random_buf(&alpha, sizeof(uint64_t));

 
   gamma0 = (x0 * y1) - alpha;
   gamma1 =  alpha;

  #ifdef VERBOSE
    std::cout << "x0 = " << x0 << std::endl;
    std::cout << "x1 = " << x1 << std::endl;
    std::cout << "gamma0 = " << gamma0 << std::endl;
  #endif

  boost::asio::write(s0, boost::asio::buffer(&x0, sizeof(x0)));
 
  boost::asio::write(s0, boost::asio::buffer(&gamma0, sizeof(gamma0)));

  boost::asio::write(s1, boost::asio::buffer(&y1, sizeof(y1)));
  boost::asio::write(s1, boost::asio::buffer(&gamma1, sizeof(gamma1)));

}

int64_t xor_to_additive(bool party, uint8_t * target_share_read, tcp::socket& sb, tcp::socket& s2)
{
    const size_t logn = 64;

    
    std::array<uint64_t, logn> b, b_blinded, b_recv;
 
    for(size_t j = 0; j < logn; ++j)
    {
        b[j] = target_share_read[j];

        #ifdef DEBUG
            uint8_t target_bit_rec;   
            boost::asio::write(sb, boost::asio::buffer(&target_share_read[j],   sizeof(uint8_t)));
            boost::asio::read(sb, boost::asio::buffer(&target_bit_rec,   sizeof(uint8_t)));
            if(target_bit_rec != target_share_read[j]) std::cout << "non-zero XOR index = " << j << std::endl;
        #endif
    }

    #ifdef DEBUG
        uint64_t b_ = binary_to_decimal(b);
        std::cout << "b_ = " << b_ << std::endl;
    #endif
    
    std::array<uint64_t, logn> c_mul;
    std::array<uint64_t, logn> d;
   

    
    uint64_t BLIND, Gamma;
    boost::asio::read(s2, boost::asio::buffer(&BLIND, sizeof(uint64_t)));
    boost::asio::read(s2, boost::asio::buffer(&Gamma, sizeof(uint64_t)));


    for(size_t j = 0; j < logn; ++j)
    {
        b_blinded[j] = b[j] + BLIND;
    }


    boost::asio::write(sb, boost::asio::buffer(&b_blinded, logn * sizeof(b_blinded[0])));
    boost::asio::read (sb, boost::asio::buffer(&b_recv,  logn * sizeof(b_recv[0])));
 
    
    #ifdef DEBUG
        std::cout << "BLIND = " << BLIND << std::endl;
        std::cout << "Gamma = " << Gamma << std::endl;
    #endif
    
    uint64_t R_share = 0;
    
    if(!party) 
    {
        for(size_t j = 0; j < logn; ++j) 
        {
            c_mul[j] =  (b[j] * b_recv[j]) + Gamma;
            d[j] = (b[j] - 2 * c_mul[j]);
            R_share += (1ULL << j) * d[j];
        }
    }

    if(party)  
    {
        for(size_t j = 0; j < logn; ++j) 
        {       
            c_mul[j] = -(BLIND * b_recv[j]) + Gamma;
            d[j] = (b[j] - 2 * c_mul[j]);
            R_share += (1ULL << j) * d[j];
        }
    }

    #ifdef DEBUG
        for(size_t j = 0; j < 1; ++j)
        {
        
            std::cout << "b = " << b[j] << std::endl;
            uint64_t mul_Rec = 0;
            boost::asio::write(sb, boost::asio::buffer(&c_mul[j], sizeof(c_mul[j])));
            boost::asio::read(sb, boost::asio::buffer(&mul_Rec, sizeof(mul_Rec)));
            std::cout << "c_mul = " << c_mul[j] << std::endl;
            mul_Rec = mul_Rec + c_mul[j];
            std::cout << "mul_Rec = " << mul_Rec << std::endl;
        }
 
        std::array<uint64_t, logn> b_reconstruction_;
        std::array<uint64_t, logn> d_reconstruction_;
            std::array<uint64_t, logn> d_recv;
        for(size_t j = 0; j < logn; ++j)
        {
            boost::asio::write(sb, boost::asio::buffer(&d[j], sizeof(d[j])));
            boost::asio::read(sb, boost::asio::buffer(&d_recv[j], sizeof(d_recv[j])));
        }

        boost::asio::write(sb, boost::asio::buffer(&b, logn * sizeof(b[0])));
        boost::asio::read (sb, boost::asio::buffer(&b_recv,  logn * sizeof(b_recv[0])));

        for(size_t j = 0; j < logn; ++j)
        {
            uint64_t d_reconstruction = d[j] + d_recv[j];
            d_reconstruction_[j] = d_reconstruction;
            uint64_t b_reconstruction = b[j] ^ b_recv[j];
            b_reconstruction_[j] = b_reconstruction;
            assert(d_reconstruction == b_reconstruction);
        }
        
        uint64_t b_value =	binary_to_decimal(b_reconstruction_);
        std::cout << "b_value = " << b_value << std::endl;
        std::cout << "logn = " << logn << std::endl;
 
        std::cout << "R_share = " << R_share << std::endl;
        R_share = binary_to_decimal(d);
        std::cout << "R_share = " << R_share << std::endl;
        uint64_t R_share_reconstruction;
        boost::asio::write(sb, boost::asio::buffer(&R_share, sizeof(R_share)));
        boost::asio::read(sb, boost::asio::buffer(&R_share_reconstruction, sizeof(R_share_reconstruction)));
        R_share_reconstruction = R_share_reconstruction + R_share;
        std::cout << "R_share_reconstruction = " << R_share_reconstruction << std::endl;
        std::cout << "b_value                = " << b_value << std::endl;
        std::cout << "d_recons		 = " << binary_to_decimal(d_reconstruction_)<< std::endl;
    #endif

    return R_share;
}


void convert_shares(size_t i, __m128i ** output, int8_t ** flags, size_t n_threads, size_t db_nitems, __m128i * final_correction_word, 
					int64_t ** leaves, int64_t ** leafbits,
					tcp::socket& sb, tcp::socket& s2, bool party)
{
	
	#ifdef DEBUG
		std::cout << "share conversion " << i << "-th, thread started runing" << std::endl << std::endl;
	#endif

	for(size_t j = 0; j < db_nitems; ++j)
	{
		if(party)
		{
			output[i][j] = -output[i][j];
			flags[i][j]  =  -flags[i][j]; 
		}
	}

	int64_t pm = 0;
	int64_t rb;

	arc4random_buf(&rb, sizeof(rb));

	for(size_t j = 0; j < db_nitems; ++j)
	{
		if(party)
		{
		  if(flags[i][j] != 0)	pm -= 1;
		}
		if(!party)
		{
		 if(flags[i][j] != 0)	pm += 1; 
		}
	}

 
	int64_t FCWshare = du_attalah_Pb(final_correction_word[i][1], pm, s2, sb);

 
	
 FCWshare+=rb;
	int64_t FCWshare_reconstruction;
 boost::asio::write(sb, boost::asio::buffer(&FCWshare, sizeof(FCWshare)));
	boost::asio::read(sb, boost::asio::buffer(&FCWshare_reconstruction, sizeof(FCWshare_reconstruction)));
	FCWshare_reconstruction = FCWshare_reconstruction + FCWshare;

 
	
	int64_t PM = pm + rb;
	int64_t PM_recv;

	boost::asio::write(sb, boost::asio::buffer(&PM, sizeof(PM)));
	boost::asio::read(sb, boost::asio::buffer(&PM_recv, sizeof(PM_recv)));
    
    int64_t * flags_  = (int64_t *)std::aligned_alloc(sizeof(node_t), db_nitems * sizeof(int64_t));

	


    shareconversion P2_shareconversion;
    P2_shareconversion.PM = pm;
    P2_shareconversion.PM_recv = PM_recv;
    P2_shareconversion.rb = rb;
    P2_shareconversion.FCWshare_reconstruction = FCWshare_reconstruction;
	
    boost::asio::write(s2, boost::asio::buffer(&P2_shareconversion, sizeof(shareconversion)));
    
    for(size_t j = 0; j < db_nitems; ++j)
	{
		leaves[i][j]  = output[i][j][0];

 

		flags_[j] = (flags[i][j] * pm) + (flags[i][j] * PM_recv) + (flags[i][j] * rb); 
 
		flags_[j] += output[i][j][1]; 

		if(!party)
		{	
		    flags_[j] -= (flags[i][j] * FCWshare_reconstruction);		
		}
		if(party)
		{ 
		    flags_[j] -= (flags[i][j] * FCWshare_reconstruction);
		}
	
		#ifdef DEBUG
		int64_t flags_rec;
		boost::asio::write(sb, boost::asio::buffer(&flags_[j], sizeof(flags_[j])));
		boost::asio::read(sb, boost::asio::buffer(&flags_rec, sizeof(flags_rec)));
		flags_rec = flags_rec + flags_[j];

		if(flags_rec != 0)
		{
			 std::cout << j << " ---> Flag Reconstruction = " << flags_rec << std::endl;
		}
		#endif
		
 
		flags[i][j] = flags_[j];
		
		if(flags[i][j] == 128 || flags[i][j] == -128) flags[i][j] = 0;
		leafbits[i][j] = flags[i][j];
		
		#ifdef DEBUG
            int8_t flags_rec2;
            boost::asio::write(sb, boost::asio::buffer(&flags[i][j], sizeof(flags[i][j])));
            boost::asio::read(sb, boost::asio::buffer(&flags_rec2, sizeof(flags_rec2)));
            flags_rec2 = flags_rec2 + flags[i][j];

            if(flags_rec2 != 0)
            {
                std::cout << j << " ---> Flag Reconstruction = " << (int) flags_rec2 << std::endl;
                if(flags_rec2 != 1) std::cout << (int) flags[i][j]  << "-> " << flags_[j] << std::endl;
            }
		#endif
	}

	 write_evalfull_outs_into_a_file(party, i, db_nitems, flags,  leaves[0], final_correction_word); 
}