//
// LiDIA - a library for computational number theory
//   Copyright (c) 1994, 1995 by the LiDIA Group
//
// File        : Fp_polarithmetic.c
// 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_fft.h>

#else

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

#endif


void Fp_polynomial::make_monic()
{
    debug_handler_l( "Fp_polynomial", "make_monic ( void )", 5 );

    if (is_monic())
	return;

    remove_leading_zeros();	//raises an error if modulus() == 0
    if (is_zero())
	lidia_error_handler( "Fp_polynomial", "make_monic ( void ) :: cannot make zero polynomial monic" );

    lidia_size_t d = degree();
    const bigint &p = modulus();
    bigint lc_inv;
    InvMod(lc_inv, coeff[d], p);
    for (lidia_size_t i = d-1; i >= 0; i--)
	MulMod(coeff[i], coeff[i], lc_inv, p);
    coeff[d].assign_one();
}
    
    


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

				operators

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

Fp_polynomial operator - (const Fp_polynomial &a)
{
  	Fp_polynomial x;
	negate(x, a);
	return x;
}

Fp_polynomial operator + (const Fp_polynomial &a, const Fp_polynomial &b)
{
  	Fp_polynomial x;
	add(x, b, a);
	return x;
}
Fp_polynomial operator + (const Fp_polynomial &a, const bigint &b)
{
  	Fp_polynomial x;
	add(x, a, b);
	return x;
}
Fp_polynomial operator + (const bigint &a, const Fp_polynomial &b)
{
  	Fp_polynomial x;
	add(x, a, b);
	return x;
}

Fp_polynomial operator - (const Fp_polynomial &a, const Fp_polynomial &b)
{
  	Fp_polynomial x;
	subtract(x, a, b);
	return x;
}
Fp_polynomial operator - (const bigint &a, const Fp_polynomial &b)
{
  	Fp_polynomial x;
	negate(x, b);
	add(x, x, a);
	return x;
}
Fp_polynomial operator - (const Fp_polynomial &a, const bigint &b)
{
  	Fp_polynomial x;
	subtract(x, a, b);
	return x;
}

Fp_polynomial operator * (const Fp_polynomial &a, const Fp_polynomial &b)
{
  	Fp_polynomial x;
	multiply(x, a, b);
	return x;
}
Fp_polynomial operator * (const bigint &a, const Fp_polynomial &b)
{
  	Fp_polynomial x;
	multiply_by_scalar(x, b, a);
	return x;
}
Fp_polynomial operator * (const Fp_polynomial &a, const bigint &b)
{
  	Fp_polynomial x;
	multiply_by_scalar(x, a, b);
	return x;
}

Fp_polynomial operator / (const Fp_polynomial &a, const Fp_polynomial &b)
{
  	Fp_polynomial x;
	divide(x, a, b);
	return x;
}
Fp_polynomial operator / (const bigint &a, const Fp_polynomial &b)
{
  	Fp_polynomial x;
	divide(x, a, b);
	return x;
}
Fp_polynomial operator / (const Fp_polynomial &a, const bigint &b)
{
  	Fp_polynomial x;
	divide(x, a, b);
	return x;
}

Fp_polynomial operator % (const Fp_polynomial &a, const Fp_polynomial &b)
{
  	Fp_polynomial x;
	remainder(x, a, b);
	return x;
}


Fp_polynomial & Fp_polynomial::operator += (const Fp_polynomial &a)
{
	add(*this, *this, a);
	return *this;
}
Fp_polynomial & Fp_polynomial::operator += (const bigint &a)
{
	add(*this, *this, a);
	return *this;
}

Fp_polynomial & Fp_polynomial::operator -= (const Fp_polynomial &a)
{   
	subtract(*this, *this, a); 
	return *this;
}
Fp_polynomial & Fp_polynomial::operator -= (const bigint &a)
{   
	subtract(*this, *this, a); 
	return *this;
}

Fp_polynomial & Fp_polynomial::operator *= (const Fp_polynomial &a)
{   
	multiply(*this, *this, a); 
	return *this;
}
Fp_polynomial & Fp_polynomial::operator *= (const bigint &a)
{   
	multiply(*this, *this, a); 
	return *this;
}

Fp_polynomial & Fp_polynomial::operator /= (const Fp_polynomial &a)
{   
	divide(*this, *this, a); 
	return *this;
}
Fp_polynomial & Fp_polynomial::operator /= (const bigint &a)
{   
	divide(*this, *this, a); 
	return *this;
}

Fp_polynomial & Fp_polynomial::operator %= (const Fp_polynomial &a)
{   
	remainder(*this, *this, a); 
	return *this;
}


bigint Fp_polynomial::operator() (const bigint & value) const
{
    debug_handler_l ( "Fp_polynomial", "operator () ( bigint )", 5 );

    lidia_size_t deg = degree();
    bigint result = 0;
    const bigint &p = modulus();

    if (is_zero())
	return result;		//return '0'
    
    if (p.is_zero())
	lidia_error_handler( "Fp_polynomial", "operator ( bigint & )::modulus = 0" );
	    
    if (deg == 0 || value.is_zero())
	result.assign( coeff[0] );
    else
	if (deg > 0)
	{
	    result.assign( coeff[deg] );
	    for (lidia_size_t i = deg - 1; i >= 0; i--)
	    {
		multiply(result, result, value);  //result *= value;
		add(result, result, coeff[i]);    //result += coeff[i];
		if (i % 2 == 0)
		    Remainder(result, result, p);
	    }
	}
    return result;
}



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

        	addition, subtraction

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

void Fp_polynomial::negate()
{
    debug_handler_l ( "Fp_polynomial", "negate ( void )", 5 );

    if (modulus().is_zero())
	lidia_error_handler( "Fp_polynomial", "negate()::modulus = 0" );
    for (lidia_size_t i = 0; i < c_length; i++)
        NegateMod(coeff[i], coeff[i], modulus());
}

void negate(Fp_polynomial &a, const Fp_polynomial &b)
{
    debug_handler_l ( "Fp_polynomial", "negate ( Fp_polynomial&, Fp_polynomial& )", 5 );

    if (b.modulus().is_zero())
	lidia_error_handler( "Fp_polynomial", "negate( Fp_polynomial&, Fp_polynomial& )::modulus = 0" );

    a.MOD = b.MOD;
    a.set_degree(b.degree());

    for (lidia_size_t i = 0; i < b.c_length; i++)
        NegateMod(a.coeff[i], b.coeff[i], b.modulus());
}

void add(Fp_polynomial &c,
            const Fp_polynomial &a, const Fp_polynomial &b)
{
    debug_handler_l ( "Fp_polynomial",
            "add ( Fp_polynomial&, Fp_polynomial&, Fp_polynomial& )", 5 );

    a.comp_modulus(b, "add");

    const bigint *ap, *bp;
    bigint *cp;
    lidia_size_t deg_a = a.degree(), deg_b = b.degree();
    lidia_size_t i, min_deg_ab, max_deg_ab;

    if (deg_a > deg_b)
    {
        max_deg_ab = deg_a;
        min_deg_ab = deg_b;
    }
    else
    {
        max_deg_ab = deg_b;
        min_deg_ab = deg_a;
    }

    c.set_degree(max_deg_ab);
    c.MOD = a.MOD;

    ap = a.coeff;
    bp = b.coeff;
    cp = c.coeff;
    const bigint & p = a.modulus();

    for (i = min_deg_ab + 1; i; i--, ap++, bp++, cp++)
        AddMod((*cp), (*ap), (*bp), p);

    if (deg_a > min_deg_ab  &&  &c != &a)
        for (i = deg_a - min_deg_ab; i; i--, cp++, ap++)
            *cp = *ap;
    else if (deg_b > min_deg_ab  &&  &c != &b)
        for (i = deg_b - min_deg_ab; i; i--, cp++, bp++)
            *cp = *bp;
    else
        c.remove_leading_zeros();
}


void add(Fp_polynomial &c, const Fp_polynomial &a, const bigint &b)
{
    debug_handler_l ( "Fp_polynomial",
            "add ( Fp_polynomial&, Fp_polynomial&, bigint& )", 5 );

    if (b.is_zero())
    {
	c.assign(a);
	return;
    }

    if (a.is_zero())
    {
        c.MOD = a.MOD;
        c.set_degree(0);
    }
    else
        c.assign(a);
	
    AddMod(c.coeff[0], c.coeff[0], b, c.modulus());

    if (c.degree() == 0 && c.coeff[0].is_zero())
        c.assign_zero();
}

void subtract(Fp_polynomial &c,
            const Fp_polynomial &a, const Fp_polynomial &b)
{
    debug_handler_l ( "Fp_polynomial",
	"subtract ( Fp_polynomial&, Fp_polynomial&, Fp_polynomial& )", 5 );

    a.comp_modulus(b, "subtract");

    const bigint *ap, *bp;
    bigint *cp;
    lidia_size_t deg_a = a.degree(), deg_b = b.degree();
    lidia_size_t i, min_deg_ab, max_deg_ab;

    if (deg_a > deg_b)
    {
        max_deg_ab = deg_a;
        min_deg_ab = deg_b;
    }
    else
    {
        max_deg_ab = deg_b;
        min_deg_ab = deg_a;
    }

    c.set_degree(max_deg_ab);
    c.MOD = a.MOD;

    ap = a.coeff;
    bp = b.coeff;
    cp = c.coeff;
    const bigint & p = a.modulus();

    for (i = min_deg_ab + 1; i; i--, ap++, bp++, cp++)
        SubMod((*cp), (*ap), (*bp), p);

    if (deg_a > min_deg_ab  && &c != &a)
        for (i = deg_a - min_deg_ab; i; i--, cp++, ap++)
            *cp = *ap;
    else if (deg_b > min_deg_ab)
        for (i = deg_b - min_deg_ab; i; i--, cp++, bp++)
            NegateMod((*cp), (*bp), p);
    else
        c.remove_leading_zeros();
}

void subtract(Fp_polynomial & x, const Fp_polynomial& a, const bigint& b)
{
    debug_handler_l ( "Fp_polynomial", "subtract( Fp_polynomial &, "
	"const Fp_polynomial&, const bigint& )", 5 );
    add(x, a, -b);
}


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

			multiplication routines

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

void multiply_by_scalar(Fp_polynomial &c,
            const Fp_polynomial &a, const bigint &b)
//b may even be any of a's or c's coefficients
{
    debug_handler_l ( "Fp_polynomial",
        "multiply_by_scalar ( Fp_polynomial&, Fp_polynomial&, bigint& )", 5 );

    if (a.modulus().is_zero())
	lidia_error_handler( "Fp_polynomial", "multiply_by_scalar ( Fp_polynomial&, Fp_polynomial&, bigint& )::modulus = 0" );

    c.MOD = a.MOD;

    const bigint& p = a.modulus();
    bigint tmp_b;
    Remainder(tmp_b, b, p);

    if (tmp_b.is_zero() || a.is_zero())
    {
        c.assign_zero();
        return;
    }
    if (tmp_b.is_one())
    {
        c.assign(a);
        return;
    }

//  if (tmp_b == p - 1) //i.e. -1
//  {
//      c.negate(a);
//      return;
//  }

    const bigint *ap;
    bigint *cp;
    lidia_size_t deg_a = a.degree();
    lidia_size_t i;
    c.set_degree(deg_a);

    for (i = 0, ap = a.coeff, cp = c.coeff; i <= deg_a; i++, ap++, cp++)
        MulMod((*cp), (*ap), tmp_b, p);
}

void multiply(Fp_polynomial& x, const Fp_polynomial& a, const Fp_polynomial& b)
{
    debug_handler_l( "Fp_polynomial", "multiply ( Fp_polynomial&, Fp_polynomial&, Fp_polynomial& )", 5 );

    a.comp_modulus(b, "multiply");

    lidia_size_t crov =
	Fp_polynomial::crossovers.fftmul_crossover(a.modulus());
    if (a.degree() > crov && b.degree() > crov)
	fft_mul(x, a, b);
    else
	plain_mul(x, a, b);
}

void square(Fp_polynomial& x, const Fp_polynomial& a)
{
    debug_handler_l( "Fp_polynomial", "square ( Fp_polynomial&, Fp_polynomial& )", 5 );

    lidia_size_t crov =
	Fp_polynomial::crossovers.fftmul_crossover(a.modulus());
    if (a.degree() > crov)  fft_sqr(x, a);
    else                    plain_sqr(x, a);
}


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

			division routines

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

void div_rem(Fp_polynomial& q, Fp_polynomial& r, const Fp_polynomial& a, const Fp_polynomial& b)
{
    debug_handler_l( "Fp_polynomial", "div_rem( Fp_polynomial&, "
		    "Fp_polynomial&, Fp_polynomial&, Fp_polynomial& )", 5 );

    a.comp_modulus(b, "div_rem");

    lidia_size_t crov =
	Fp_polynomial::crossovers.fftdiv_crossover(a.modulus());
    if (b.degree() > crov && a.degree() - b.degree() > crov)
	fft_div_rem(q, r, a, b);
    else
	plain_div_rem(q, r, a, b);
}


void divide(Fp_polynomial& q, const Fp_polynomial& a, const Fp_polynomial& b)
{
    debug_handler_l( "Fp_polynomial", "divide ( Fp_polynomial&, Fp_polynomial&, Fp_polynomial& )", 5 );

    a.comp_modulus(b, "divide");

    lidia_size_t crov =
	Fp_polynomial::crossovers.fftdiv_crossover(a.modulus());
    if (b.degree() > crov && a.degree() - b.degree() > crov)
	fft_div(q, a, b);
    else
	plain_div(q, a, b);
}


void divide(Fp_polynomial& q, const Fp_polynomial& a, const bigint& b)
{
    debug_handler_l( "Fp_polynomial", "divide ( Fp_polynomial&, Fp_polynomial&, bigint& )", 5 );
    bigint tmp;
    InvMod(tmp, b, a.modulus());	//raises error if tmp == 0
    multiply_by_scalar(q, a, tmp);
}


void remainder(Fp_polynomial& r, const Fp_polynomial& a, const Fp_polynomial& b)
{
    debug_handler_l( "Fp_polynomial", "remainder ( Fp_polynomial&, Fp_polynomial&, Fp_polynomial& )", 5 );

    a.comp_modulus(b, "remainder");

    lidia_size_t crov =
	Fp_polynomial::crossovers.fftdiv_crossover(a.modulus());
    if (b.degree() > crov && a.degree() - b.degree() > crov)
	fft_rem(r, a, b);
    else
	plain_rem(r, a, b);
}


void invert(Fp_polynomial& x, const Fp_polynomial& a, lidia_size_t m)
{
    debug_handler_l( "Fp_polynomial", "inv( Fp_polynomial&, Fp_polynomial&, lidia_size_t )", 5 );

    lidia_size_t crov = comparator<lidia_size_t>::max(
	Fp_polynomial::crossovers.inv_crossover(a.modulus()),
	Fp_polynomial::crossovers.log2_newton_crossover(a.modulus()) );
    if (&x == &a)
    {
	Fp_polynomial la(a);
	if (m > crov)  newton_inv(x, la, m);
	else           plain_inv(x, la, m);
    }
    else
    {
	if (m > crov)  newton_inv(x, a, m);
	else           plain_inv(x, a, m);
    }
}





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

		Modular Arithmetic without pre-conditioning

***************************************************************/
// arithmetic mod f.
// all inputs and outputs are polynomials of degree less than deg(f).
// ASSUMPTION: f is assumed monic, and deg(f) > 0.
// ALIAS RESTRICTIONS: f (and exponent e) can not alias an output.


void multiply_mod(Fp_polynomial& x, const Fp_polynomial& a, const Fp_polynomial& b, const Fp_polynomial& f)
{
    debug_handler_l( "Fp_polynomial", "multiply_mod( Fp_polynomial&, Fp_polynomial&, Fp_polynomial&, Fp_polynomial& )", 6 );

    a.comp_modulus(b, "multiply_mod");
    a.comp_modulus(f, "multiply_mod");

    Fp_polynomial t;
    multiply(t, a, b);
    remainder(x, t, f);
}


void square_mod(Fp_polynomial& x, const Fp_polynomial& a, const Fp_polynomial& f)
{
    debug_handler_l( "Fp_polynomial", "square_mod( Fp_polynomial&, Fp_polynomial&, Fp_polynomial& )", 6 );

    a.comp_modulus(f, "square_mod");

    Fp_polynomial t;
    square(t, a);
    remainder(x, t, f);
}


void multiply_by_x_mod(Fp_polynomial& h, const Fp_polynomial& a, const Fp_polynomial& f)
{
    debug_handler_l( "Fp_polynomial", "multiply_by_x_mod( Fp_polynomial&, Fp_polynomial&, Fp_polynomial& )", 6 );

    a.comp_modulus(f, "multiply_by_x_mod");
    h.MOD = f.MOD;

    lidia_size_t i, n, m;
    bigint* hh;
    const bigint *aa, *ff;
    bigint t, z;

    n = f.degree();
    m = a.degree();

    if (m < n-1)
    {
	h.set_degree(m+1);
	hh = h.coeff;
	aa = a.coeff;
	for (i = m+1; i >= 1; i--)
	    hh[i].assign( aa[i-1] );
	hh[0].assign_zero();
    }
    else
    {
	if (&f == &h)
	{//allows f to alias output
	    h.assign_zero();
	    return;
	}
	if (m >= n || !f.is_monic())
	{
	    shift_left(h,a,1);
	    remainder(h, h, f);
	    return;
	}
	//now, m = n-1
	const bigint& p = f.modulus();

	h.set_degree(n-1);
	hh = h.coeff;
	aa = a.coeff;
	ff = f.coeff;
	negate(z, aa[n-1]);
	for (i = n-1; i >= 1; i--)
	{
	    MulMod(t, z, ff[i], p);
	    AddMod(hh[i], aa[i-1], t, p);
	}
	MulMod(hh[0], z, ff[0], p);
	h.remove_leading_zeros();
    }
}


void invert_mod(Fp_polynomial& x, const Fp_polynomial& a, const Fp_polynomial& f)
{
    debug_handler_l( "Fp_polynomial", "invert_mod( Fp_polynomial&, Fp_polynomial&, Fp_polynomial& )", 6 );

    a.comp_modulus(f, "invert_mod");

    Fp_polynomial d, t;
    xgcd(d, x, t, a, f);
    if (!d.is_one())
	lidia_error_handler( "Fp_polynomial", "invert_mod( Fp_polynomial&, "
	"Fp_polynomial&, Fp_polynomial& )::can't compute multiplicative "
	"inverse");
}


bool invert_mod_status(Fp_polynomial& x, const Fp_polynomial& a, const Fp_polynomial& f)
{
    debug_handler_l( "Fp_polynomial", "invert_mod_status( Fp_polynomial&, Fp_polynomial&, Fp_polynomial& )", 6 );

    a.comp_modulus(f, "invert_mod_status");

    Fp_polynomial d, t;
    xgcd(d, x, t, a, f);
    if (!d.is_one())
	x.assign( d );
    return ( d.is_one() );
}


// WARNING: obsolete.  Use power_mod with poly_modulus (see below).
void power_mod(Fp_polynomial& h, const Fp_polynomial& g, const bigint& e, const Fp_polynomial& f)
{
    debug_handler_l( "Fp_polynomial", "power_mod( Fp_polynomial&, Fp_polynomial&, bigint&, Fp_polynomial& )", 6 );

    g.comp_modulus(f, "power_mod");

    Fp_polynomial lg(g);
    int n = e.bit_length();

    h.MOD = f.MOD;
    h.set_max_degree( f.degree() );
    h.assign_one();

    for (int i = n - 1; i >= 0; i--)
    {
	square_mod(h, h, f);
	if (e.bit(i))
	    multiply_mod(h, h, lg, f);
    }
}


// WARNING: obsolete.  Use power_mod with poly_modulus (see below).
void power_x_mod(Fp_polynomial& h, const bigint& e, const Fp_polynomial& f)
{
    debug_handler_l( "Fp_polynomial", "power_x_mod( Fp_polynomial&, bigint&, Fp_polynomial& )", 6 );

    int n = e.bit_length();

    h.MOD = f.MOD;
    h.set_max_degree(f.degree());
    h.assign_one();

    for (int i = n - 1; i >= 0; i--)
    {
	square_mod(h, h, f);
	if (e.bit(i))
	    multiply_by_x_mod(h, h, f);
    }
}


// WARNING: obsolete.  Use power_mod with poly_modulus (see below).
void power_x_plus_a_mod(Fp_polynomial& h, const bigint& a, const bigint& e, const Fp_polynomial& f)
{
    debug_handler_l( "Fp_polynomial", "power_x_plus_a_mod( Fp_polynomial&, bigint&, bigint&, Fp_polynomial& )", 6 );

    lidia_size_t j = f.degree()-1;
    Fp_polynomial t1, t2;
    t1.set_max_degree(j);
    t2.set_max_degree(j);

    bigint la(a);	//static
    int n = e.bit_length();

    h.MOD = f.MOD;
    h.set_max_degree(j);
    h.assign_one();

    for (int i = n - 1; i >= 0; i--)
    {
	square_mod(h, h, f);
	if (e.bit(i))
	{
	    multiply_by_x_mod(t1, h, f);
	    multiply_by_scalar(t2, h, la);
	    add(h, t1, t2);
	}
    }
}






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

				   gcd's

****************************************************************/
//see  "gcd.c"




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

				other functions

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


// x = a % X^m
void trunc(Fp_polynomial &x, const Fp_polynomial &a, lidia_size_t m)
{
    debug_handler_l ( "Fp_polynomial",
            "trunc ( Fp_polynomial&, Fp_polynomial&, lidia_size_t )", 5 );
    if (&x == &a)
    {
        if (x.c_length > m)
        {
             x.c_length = m;
             x.remove_leading_zeros();
        }
    }
    else
    {
        x.MOD = a.MOD;
        lidia_size_t n = comparator<lidia_size_t>::min(a.c_length, m);
        x.set_degree(n-1);

        bigint* xp = x.coeff;
        const bigint* ap = a.coeff;

        for (lidia_size_t i = 0; i < n; i++)
            xp[i].assign(ap[i]);

        x.remove_leading_zeros();
    }
}


// x = a/X^n
void shift_right(Fp_polynomial &x, const Fp_polynomial &a, lidia_size_t n)
{
    debug_handler_l ( "Fp_polynomial",
            "shift_right ( Fp_polynomial&, Fp_polynomial&, lidia_size_t )", 5 );

    x.MOD = a.MOD;
    lidia_size_t i, da = a.degree();

    if (da < n)
    {
        x.assign_zero();
        return;
    }

    if (&x != &a)
        x.set_max_degree(da-n);

    for (i = 0; i <= da-n; i++)
        x.coeff[i].assign(a.coeff[i+n]);

    x.c_length = da - n + 1;
}


// x = a*X^n
void shift_left(Fp_polynomial &x, const Fp_polynomial &a, lidia_size_t n)
{
    debug_handler_l ( "Fp_polynomial",
            "shift_left ( Fp_polynomial&, Fp_polynomial&, lidia_size_t )", 5 );

    x.MOD = a.MOD;
    if (a.is_zero())
    {
        x.assign_zero();
        return;
    }

    lidia_size_t i, m = a.c_length;
    x.set_degree(m+n-1);

    for (i = m-1; i >= 0; i--)
        x.coeff[i+n].assign(a.coeff[i]);

    for (i = 0; i < n; i++)
        (x.coeff[i]).assign_zero();
}


// x = derivative of a
void derivative(Fp_polynomial &x, const Fp_polynomial &a)
{
    debug_handler_l ( "Fp_polynomial",
                "derivative ( Fp_polynomial&, Fp_polynomial& )", 5 );

    const bigint& p = a.modulus();
    if (p.is_zero())
	lidia_error_handler( "Fp_polynomial", "derivative ( Fp_polynomial&, Fp_polynomial& )::modulus = 0" );

    x.MOD = a.MOD;
    lidia_size_t d = a.degree();
    if (d <= 0)
    {
        x.assign_zero();
        return;
    }

    x.set_degree(d - 1);
    for (lidia_size_t i = 0; i < d; i++)
    {
        multiply(x.coeff[i], a.coeff[i+1], i+1);
        Remainder(x.coeff[i], x.coeff[i], p);
    }
    x.remove_leading_zeros();
}


// computes x = a mod X^m-1
void cyclic_reduce(Fp_polynomial& x, const Fp_polynomial& a, lidia_size_t m)
{
    debug_handler_l( "Fp_polynomial", "cyclic_reduce( Fp_polynomial&, Fp_polynomial&, lidia_size_t )", 6 );
    lidia_size_t n = a.degree();
    lidia_size_t i, j;
    bigint accum;  //static
    const bigint& p = a.modulus();

    if (p.is_zero())
	lidia_error_handler( "Fp_polynomial", "cyclic_reduce( Fp_polynomial&, Fp_polynomal&, lidia_size_t )::modulus = 0" );

    if (n < m)
    {
        x.assign( a );
        return;
    }

    if (&x != &a)
    {   
        x.MOD = a.MOD;
        x.set_degree(m-1);
    }

    for (i = 0; i < m; i++)
    {
        accum.assign( a.coeff[i] );
        for (j = i + m; j <= n; j += m)
            add(accum, accum, a.coeff[j]);
        Remainder(x.coeff[i], accum, p);
    }

    if (&x == &a)
        x.set_degree(m-1);

    x.remove_leading_zeros();
}


void power(Fp_polynomial &x, const Fp_polynomial &a, lidia_size_t e)
{
    if (e < 0)
	lidia_error_handler("Fp_polynomial", "power(Fp_polynomial&,"
	"Fp_polynomial&, lidia_size_t)::exponent must not be negative");

    if (e == 1)
    {
	x.assign(a);
	return;
    }

    x.MOD = a.MOD;
    x.assign_one();

    Fp_polynomial m(a);
    int n = integer_log(e);
    x.set_max_degree(a.degree() * e);

    for (int i = n - 1; i >= 0; i--)
    {
	square(x, x);
	if (e & (1<<i))
	    multiply(x, x, m);
    }
}


void
add_multiple(Fp_polynomial &f, const Fp_polynomial &g, const bigint &s,
	lidia_size_t n, const Fp_polynomial &h)
//f = g + s*x^n*h
{
    g.comp_modulus(h, "add_multiple");
    if (n < 0)
	lidia_error_handler( "Fp_polynomial",
	"add_multiple( ... )::bad argument" );
    
    if (h.is_zero() || s.is_zero())
    {
	f.assign(g);
	return;
    }
    if (g.is_zero())
    {
	shift_left(f, h, n);
	return;
    }
//    if (s.is_one() && n == 0)
//    {
//	add(f, g, h);
//	return;
//    }


    const bigint *gp, *hp;
    bigint *fp;

    lidia_size_t deg_g = g.degree(), deg_h = h.degree();
    lidia_size_t i, max_deg, min_deg;

    if (deg_g < deg_h+n)
    {
	min_deg = deg_g;
	max_deg = deg_h + n;
    }
    else
    {
	min_deg = deg_h + n;
	max_deg = deg_g;
    }

    Fp_polynomial hh;
    if (&f == &h)
    {
	hh.assign(h);
	hp = hh.coeff;
    }
    else
	hp = h.coeff;
    f.set_degree(max_deg);
    f.MOD = g.MOD;
    fp = f.coeff;
    gp = g.coeff;

    const bigint & p = g.modulus();
    bigint tmp;

    if (&f != &g)
    {
	fp = f.coeff;
	gp = g.coeff;
	for (i = n; i; i--)
	    *(fp++) = *(gp++);
    }
    else
	gp = fp = f.coeff + n;

    for (i = min_deg-n+1; i; i--)
    {
	multiply(tmp, s, *(hp++));
	AddMod(*(fp++), *(gp++), tmp, p);
    }

    if (deg_h + n > min_deg)
	for (i = max_deg - min_deg; i; i--)
	    MulMod(*(fp++), *(hp++), s, p);
    else if (deg_g > min_deg  &&  &f != &g)
	for (i = max_deg - min_deg; i; i--)
	    *(fp++) = *(gp++);
    else
	f.remove_leading_zeros();
}
