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


#if defined(HAVE_MAC_DIRS) || defined(__MWERKS__)

#include <LiDIA:Fp_polynomial.h>
#include <LiDIA:Fp_polynomial_util.h>

#else

#include <LiDIA/Fp_polynomial.h>
#include <LiDIA/Fp_polynomial_util.h>

#endif




/***************************************************************
	This File contains the implementation of the functions
	- gcd, xgcd
	- plain_gcd, plain_xgcd (both always use "classical" algorithms)
	- and some auxiliary functions 
****************************************************************/


/***************************************************************
				    auxiliary functions
	all assume that all input parameters have correct modulus
****************************************************************/

void plain_rem_vec(Fp_polynomial& r, const Fp_polynomial& a, const Fp_polynomial& b, base_vector<bigint>& x)
//special version of plain_rem, uses memory offered by vector x
//about 10% faster
//assumes x.size() > a.degree()+1
//cmp. plain_div_rem_vec in file poly_matrix.c
{
	debug_handler( "Fp_polynomial", "plain_rem_vec ( Fp_polynomial&, Fp_polynomial&, Fp_polynomial&, base_vector<bigint>& )" );

	lidia_size_t da, db, dq, i, j, lc_is_one;
	const bigint & p = a.modulus();

	bigint LCInv, t;
	bigint s;	//static

	da = a.degree();
	db = b.degree();
	dq = da - db;

	if (db < 0)
		lidia_error_handler( "Fp_polynomial", "plain_rem_vec( ... )::division by zero" );

	if (dq < 0)
	{
		r.assign( a );
		return;
	}

	const bigint *bp = b.coeff;
	if (bp[db].is_one())
		lc_is_one = 1;
	else
	{
		lc_is_one = 0;
		InvMod(LCInv, bp[db], p);
	}

	bigint *xp = &x[0];			//x.get_data_address();
	for (i = 0; i <= da; i++)
		xp[i].assign( a.coeff[i] );

	for (i = dq; i >= 0; i--)
	{
	  	Remainder(t, xp[i+db], p);
		if (!lc_is_one)
			MulMod(t, t, LCInv, p);
		negate(t, t);

		for (j = db-1; j >= 0; j--)
		{
			multiply(s, t, bp[j]);
			add(xp[i+j], xp[i+j], s);
		}
	}

	r.set_degree(db-1);
	r.MOD = a.MOD;
	for (i = 0; i < db; i++)
		Remainder(r.coeff[i], xp[i], p);
	r.remove_leading_zeros();
}


void half_gcd(Fp_polynomial& U, Fp_polynomial& V)
{
	debug_handler( "gcd.c", "half_gcd( Fp_polynomial&, Fp_polynomial& )" );

	lidia_size_t du = U.degree();
	lidia_size_t d_red = (du+1)/2;
	if (V.is_zero() || V.degree() <= du - d_red)
		return;

	lidia_size_t d1 = (d_red + 1)/2;
	if (d1 < 1)
		d1 = 1;
	if (d1 >= d_red)
		d1 = d_red - 1;

	poly_matrix M1(V.MOD);
	M1.half_gcd(U, V, d1);
	M1.multiply(U, V);

	lidia_size_t d2 = V.degree() - du + d_red;

	if (V.is_zero() || d2 <= 0)
		return;

	M1.kill();

	Fp_polynomial Q;
	div_rem(Q, U, U, V);
	swap(U, V);

	poly_matrix M2(V.MOD);
	M2.half_gcd(U, V, d2);
	M2.multiply(U, V);
}




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

						gcd

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

void gcd(Fp_polynomial& d, const Fp_polynomial& u, const Fp_polynomial& v)
{
	debug_handler( "Fp_polynomial", "gcd( Fp_polynomial&, Fp_polynomial&, Fp_polynomial& )" );

	if (u.MOD != v.MOD)
	  	lidia_error_handler_c( "Fp_polynomial", "gcd( Fp_polynomial&, "
		"Fp_polynomial&, Fp_polynomial& )::wrong moduli",
		cout<<"("<<u.modulus()<<") != ("<<v.modulus()<<")"<<endl; );
		
	Fp_polynomial u1(u);
	Fp_polynomial v1(v);


	if (u1.degree() == v1.degree())
	{
		if (u1.is_zero())
		{
		  	d.MOD = v.MOD;
			d.assign_zero();
			return;
		}
		remainder(v1, v1, u1);
	}
	else
		if (u1.degree() < v1.degree())
			swap(u1, v1);


	// deg(u1) > deg(v1)
	lidia_size_t crov =
	    Fp_polynomial::crossovers.gcd_crossover(u.modulus());
	while (u1.degree() >= crov && !v1.is_zero())
	{
		half_gcd(u1, v1);
		if (!v1.is_zero())
		{
			remainder(u1, u1, v1);
			 swap(u1, v1);
		}
	}

	plain_gcd(d, u1, v1);
}

// should re-write the following
void plain_gcd(Fp_polynomial& x, const Fp_polynomial& a, const Fp_polynomial& b)
{
	debug_handler( "Fp_polynomial", "plain_gcd( Fp_polynomial&, Fp_polynomial&, Fp_polynomial&" );

	if (a.MOD != b.MOD)
	  	lidia_error_handler_c( "Fp_polynomial", "plain_gcd( Fp_polynomial&, "
		"Fp_polynomial&, Fp_polynomial&::wrong moduli",
		cout<<"("<<a.modulus()<<") != ("<<b.modulus()<<")"<<endl; );
		
	if (b.is_zero())
		x.assign( a );
	else
		if (a.is_zero())
			x.assign( b );
		else
		{
			lidia_size_t n = comparator<lidia_size_t>::max(a.degree(),b.degree()) + 1;
			Fp_polynomial u(a);
			Fp_polynomial v(b);

			base_vector<bigint> tmp( n, FIXED );

			do
			{
				plain_rem_vec(u, u, v, tmp);
				swap(u, v);
			} while (!v.is_zero());

		x.assign( u );
	}

	if (x.is_zero())
		return;
	if ( (x.lead_coeff()).is_one() )
		return;

   /* make gcd monic */

	bigint t;	//static

	InvMod(t, x.lead_coeff(), a.modulus());
	multiply_by_scalar(x, x, t);
}



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

						xgcd

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


void xgcd(Fp_polynomial& d, Fp_polynomial& s, Fp_polynomial& t, const Fp_polynomial& a, const Fp_polynomial& b)
{
	debug_handler( "Fp_polynomial", "xgcd( Fp_polynomial&, Fp_polynomial&, Fp_polynomial&, Fp_polynomial&, Fp_polynomial& )" );

	if (a.MOD != b.MOD)
	  	lidia_error_handler_c( "Fp_polynomial", "xgcd( Fp_polynomial&, "
		"Fp_polynomial&, Fp_polynomial&, Fp_polynomial&, Fp_polynomial& )::wrong moduli",
		cout<<"("<<a.modulus()<<") != ("<<b.modulus()<<")"<<endl; );
	
	if (a.is_zero() && b.is_zero())
	{
	  	d.MOD = a.MOD;
		s.MOD = a.MOD;
		t.MOD = a.MOD;
		
		d.assign_zero();
		s.assign_zero();
		t.assign_zero();
		return;
	}

	Fp_polynomial U(a), V(b), Q;
	const bigint & p = a.modulus();
	lidia_size_t flag = 0;

	if (U.degree() == V.degree())
	{
		div_rem(Q, U, U, V);
		swap(U, V);
		flag = 1;
	}
	else
		if (U.degree() < V.degree())
		{
			swap(U, V);
			flag = 2;
		}

	poly_matrix M(a.MOD);

	M.xhalf_gcd(U, V, U.degree()+1);

	d = U;

	if (flag == 0)
	{
		s = M(0,0);
		t = M(0,1);
	}
	else
		if (flag == 1)
		{
			s = M(0,1);
			multiply(t, Q, M(0,1));
			subtract(t, M(0,0), t);
		}
		else
		{  /* flag == 2 */
			s = M(0,1);
			t = M(0,0);
		}

	// normalize

	bigint w;	//static
	InvMod(w, d.lead_coeff(), p);
	multiply_by_scalar(d, d, w);
	multiply_by_scalar(s, s, w);
	multiply_by_scalar(t, t, w);
}


// should re-write the following
void plain_xgcd(Fp_polynomial& d, Fp_polynomial& s, Fp_polynomial& t, const Fp_polynomial& a, const Fp_polynomial& b)
{
	debug_handler( "Fp_polynomial", "plain_xgcd( Fp_polynomial&, Fp_polynomial&, Fp_polynomial&, Fp_polynomial&, Fp_polynomial&" );

    if (a.MOD != b.MOD)
		lidia_error_handler_c( "Fp_polynomial", "plain_xgcd( Fp_polynomial&, "
        "Fp_polynomial&, Fp_polynomial&, Fp_polynomial&, Fp_polynomial& )::wrong moduli",
        cout<<"("<<a.modulus()<<") != ("<<b.modulus()<<")"<<endl; );

	if (b.is_zero())
	{
	  	s.MOD = a.MOD;
		t.MOD = a.MOD;
		
		s.assign_one();
		t.assign_zero();
		d.assign( a );
	}
	else if (a.is_zero())
	{
	  	s.MOD = a.MOD;
		t.MOD = a.MOD;

		s.assign_zero();
		t.assign_one();
		d.assign( b );
	}
	else
	{
		lidia_size_t e = comparator<lidia_size_t>::max(a.degree(), b.degree());

		Fp_polynomial u(a);
		Fp_polynomial v(b);
		Fp_polynomial temp, u0, v0, u1, v1, u2, v2, q, r;

		temp.set_max_degree(e);
		u0.set_max_degree(e);
		v0.set_max_degree(e);
		u1.set_max_degree(e);
		v1.set_max_degree(e);
		u2.set_max_degree(e);
		v2.set_max_degree(e);
		q.set_max_degree(e);
		r.set_max_degree(e);

		u1.MOD = a.MOD;
		u2.MOD = a.MOD;
		v1.MOD = a.MOD;
		v2.MOD = a.MOD;

		u1.assign_one(); 	v1.assign_zero();
		u2.assign_zero(); 	v2.assign_one();

		do
		{
			div_rem(q, r, u, v);
			u.assign( v );			//u = v;
			v.assign( r );			//v = r;
			u0.assign( u2 );		//u0 = u2;
			v0.assign( v2 );		//v0 = v2;
			multiply(temp, q, u2);
			subtract(u2, u1, temp);
			multiply(temp, q, v2);
			subtract(v2, v1, temp);
			u1.assign( u0 );		//u1 = u0;
			v1.assign( v0 );		//v1 = v0;
		} while (!v.is_zero());

		d.assign( u );			//d = u;
		s.assign( u1 );			//s = u1;
		t.assign( v1 );			//t = v1;
	}

	if (d.is_zero())
		return;
	if ( (d.lead_coeff()).is_one() )
		return;

	/* make gcd monic */
	bigint z;	//static

	InvMod(z, d.lead_coeff(), a.modulus());
	multiply_by_scalar(d, d, z);
	multiply_by_scalar(s, s, z);
	multiply_by_scalar(t, t, z);
}

