//
// LiDIA - a library for computational number theory
//   Copyright (c) 1994, 1995 by the LiDIA Group
//
// File        : poly_multiplier.c
// Author      : Victor Shoup, Thomas Pfahler (TPf)
// Last change : TPf, Feb 29, 1996, initial version
//

#if defined(HAVE_MAC_DIRS) || defined(__MWERKS__)
#include <LiDIA:poly_multiplier.h>
#else
#include <LiDIA/poly_multiplier.h>
#endif


/******************************************************************

			  class poly_multiplier

*******************************************************************/


void poly_multiplier::build( const Fp_polynomial& b, const poly_modulus& F )
{
	debug_handler( "poly_multiplier", "build ( Fp_polynomial&, poly_modulus& )" );

	mod = &F;
	
	lidia_size_t db;
	lidia_size_t n = F.deg();

	remainder(pol_b, b, F);
	db = pol_b.degree();
	
	if (!F.use_fft() || db <= F.crov)
	{
		UseFFT = false;
		return;
	}

	UseFFT = true;

	modular_fft_rep R1(F.l, pol_b.MOD);
	Fp_polynomial P1;
	P1.set_max_degree(n-1);

	B2.init(F.k, F.F);
//	B2.set_size(F.k);

	for (lidia_size_t index = 0; index < R1.number_of_primes(); index++)
	{
		R1.to_modular_fft_rep(pol_b, index);
		reduce(B2, R1, F.k, index);		//initializing B2
		multiply(R1, F.HRep, R1, index);
		R1.from_modular_fft_rep(n-1, 2*n-3, index);
	}
	R1.get_result(P1, n-1, 2*n-3);
	B1.init(F.l, F.F);
//	B1.set_size(F.l);
	B1.to_fft_rep(P1);		//initializing B1

}



// If you need to compute a * b % f for a fixed b, but for many a's
// (for example, computing powers of b modulo f), it is
// much more efficient to first build a poly_multiplier B for b,
// and then use the routine below.
// x = (a * b) % f
void multiply(Fp_polynomial& x, const Fp_polynomial& a, const poly_multiplier& B,
					  const poly_modulus& F)
{
	debug_handler( "poly_multiplier", "multiply( Fp_polynomial&, Fp_polynomial&, poly_multiplier&, poly_modulus& )" );

	a.comp_modulus(F.modulus(), "multiply");

	if (!B.uses(F) || B.mod == 0)
	  	lidia_error_handler( "poly_multiplier", "multiply( Fp_polynomial&, Fp_polynomial&, poly_multiplier&, poly_modulus& )::wrong poly_modulus, or poly_multiplier not initialized" );

	lidia_size_t n = F.deg();
	lidia_size_t da, index;

	da = a.degree();

	if (da >= n)
		lidia_error_handler( "poly_multiplier", "multiply( Fp_polynomial&, Fp_polynomial&, poly_multiplier&, poly_modulus& )::degree of Fp_polynomial must be < degree of poly_modulus" );
	
	if (da <= F.crov || !B.UseFFT || !F.use_fft())
	{
		Fp_polynomial P1;
		plain_mul(P1, a, B.pol_b);
		plain_rem(x, P1, F.modulus());
		return;
	}

	Fp_polynomial P1;
	Fp_polynomial P2;
	P1.set_max_degree(n-1);
	P2.set_max_degree(n-1);

	modular_fft_rep R1(F.HRep), R2(F.HRep);
	fft_rep R3(F.FRep);

	for (index = 0; index < R1.number_of_primes(); index++)
	{
		R1.to_modular_fft_rep(a, index);
		reduce(R3, R1, F.k, index);
		multiply(R2, B.B1, R1, index);
		R2.from_modular_fft_rep(n-1, 2*n-3, index);
	}
	R2.get_result(P1, n-1, 2*n-3);
	multiply(R3, B.B2, R3);

	R1.set_size(F.k);
	R2.set_size(F.k);
	for (index = 0; index < R1.number_of_primes(); index++)
	{
		R1.to_modular_fft_rep(P1, index);
		multiply(R1, F.FRep, R1, index);
		subtract(R2, R3, R1, index);

		R2.from_modular_fft_rep(0, n-1, index);
	}

	R2.get_result(x, 0, n-1);
}

