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



bigint Fp_polynomial::ZERO = 0;

fft_table *Fp_polynomial::fft_table_list = 0;



Fp_pol_ref& Fp_pol_ref::operator= (const bigint &a)	// L-VALUE
{
    p.set_coefficient(a, ix);
    return *this;
}

void Fp_pol_ref::assign_one()
{
    p.set_coefficient(ix);
}

void Fp_pol_ref::assign_zero()
{
    p.set_coefficient((bigint)0, ix);
}

void Fp_pol_ref::assign(const bigint& a)
{
    p.set_coefficient(a, ix);
}

Fp_pol_ref::operator bigint()				// R-VALUE
{
    if (p.modulus().is_zero())
	lidia_error_handler( "Fp_polynomial", "operator[]::modulus is zero" );

    if ( ix < 0 )
        lidia_error_handler_c ( "Fp_polynomial", "operator[]::index out of range",
                    cout<<"index = "<<ix<<endl; );

    if ( ix > p.degree())
	return (bigint)0;
	
    return p.coeff[ix];
}











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

		Constructors, Destructors, and Basic Functions 

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


Fp_polynomial::Fp_polynomial(const Fp_polynomial & a) :
	c_length(a.c_length),
	c_alloc(a.c_length)
{
  	debug_handler_l( "Fp_polynomial", "Fp_polynomial( const Fp_polynomial& )", 1 );

	if (a.modulus().is_zero())
	  	lidia_error_handler( "Fp_polynomial", "Fp_polynomial( const Fp_polynomial& )::modulus = 0" );
	
	coeff = new bigint[a.c_length];
	if (!coeff)
	  	lidia_error_handler( "Fp_polynomial", "Fp_polynomial( Fp_polynomial& )::out of memory" );

	//for (lidia_size_t i = 0; i < a.c_length; i++)
	// 	coeff[i].assign(a.coeff[i]);
	bigint *xp = coeff;
	const bigint *ap = a.coeff;
	for (lidia_size_t i = 0; i < a.c_length; i++,ap++,xp++)
		xp->assign(*ap);
	
	MOD = a.MOD;
}


Fp_polynomial::~Fp_polynomial()
{
  	debug_handler_l( "Fp_polynomial", "~Fp_polynomial()", 1 );
	delete[] coeff;
}


void Fp_polynomial::kill()
{
  	debug_handler_l( "Fp_polynomial", "kill ( void )", 1 );
	delete[] coeff;
	coeff = 0;
	c_alloc = 0;
	c_length = 0;
	MOD.del();
}


void Fp_polynomial::set_modulus(const bigint& p)
{
  	debug_handler_l( "Fp_polynomial", "set_modulus( const bigint& )", 2);
	if (modulus() != p)
	{
		kill();
		if (p < 2)
	  		lidia_error_handler( "Fp_polynomial", "set_modulus( bigint& )::modulus must be >=2" );
//		if (!is_prime(p,8))		/* speed ! */
//		  	lidia_error_handler( "Fp_polynomial", "set_modulus( bigint& )::modulus must be prime" );
		MOD.set(p);
	}
	else
	  	assign_zero();
}


void Fp_polynomial::comp_modulus(const Fp_polynomial& x, const char* fctname) const
{
    if (MOD == x.MOD || modulus().is_zero())
	return;
    char msg[60];
    strcpy(msg, fctname);
    strcat(msg, "::different moduli");
    lidia_error_handler( "Fp_polynomial", msg);
}


void Fp_polynomial::set_max_degree(lidia_size_t n)
{
// nothing happens if degree() > n
  	debug_handler_l( "Fp_polynomial", "set_max_degree ( lidia_size_t )", 2 );
	n++;
	if (c_alloc < n)
	{
	  	bigint* tmp = new bigint[n];
		if (!tmp)
		    lidia_error_handler( "Fp_polynomial", "set_max_degree :: out of memory" );

		bigint *xp = tmp, *ap = coeff;
		for (lidia_size_t i = 0; i < c_length; i++,xp++,ap++)
			xp->assign(*ap);
		  	//swap(*xp, *ap);
		delete[] coeff;
		coeff = tmp;
		c_alloc = n;
	}
}
  	

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

        routines for manipulating coefficients

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

// a = rep[i], or zero if i not in range
void Fp_polynomial::get_coefficient(bigint& a, lidia_size_t i) const
{
    debug_handler_l ( "Fp_polynomial", "get_coefficient ( bigint&, lidia_size_t )", 3 );
    if ( i<0 || i>degree() )
        a.assign_zero();
    else
        a.assign(coeff[i]);
}


// returns coeff[ degree() ]
// zero if a == 0
const bigint& Fp_polynomial::lead_coeff() const
{
    debug_handler_l ( "Fp_polynomial", "lead_coeff ( void )", 3 );
    if ( is_zero() )
        return ZERO;
    else
        return coeff[degree()];
}

//returns coeff[0]
// zero if a == 0
const bigint& Fp_polynomial::const_term() const
{
    debug_handler_l ( "Fp_polynomial", "const_term ( void )", 3 );
    if ( is_zero() )
        return ZERO;
    else
        return coeff[0];
}

// rep[i] = a, error is raised if i < 0
void Fp_polynomial::set_coefficient(const bigint& a, lidia_size_t i)
{
    debug_handler_l ( "Fp_polynomial", "set_coefficient ( bigint&, lidia_size_t )", 2 );

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

    if (i < 0)
        lidia_error_handler_c( "Fp_polynomial",
                        "set_coefficient( bigint&, lidia_size_t )::negative index",
                        cout<<"index = "<<i<<endl; );
    lidia_size_t j, m = degree();

    if (i>m)
    {
	  	bigint tmp;
		Remainder(tmp, a, modulus());
	  	if (!tmp.is_zero())
		{
        	debug_handler_l ( "Fp_polynomial", "set_coefficient( bigint&, lidia_size_t )::enlarging coefficient vector", 1 );
			set_degree(i);
        	for (j = m+1; j < i; j++)
            	coeff[j].assign_zero();
		coeff[i].assign(tmp);
		}
    }
    else
    {
        Remainder(coeff[i], a, modulus());
        if ( i == m )
            remove_leading_zeros();
    }
}

// coeff[i] = 1, error is raised if i < 0
void Fp_polynomial::set_coefficient(lidia_size_t i)
{
    debug_handler_l ( "Fp_polynomial", "set_coefficient ( lidia_size_t )", 2 );

   if (modulus().is_zero())
         lidia_error_handler( "Fp_polynomial",
                  "set_coefficient( lidia_size_t )::modulus = 0" );

    if (i < 0)
        lidia_error_handler_c ( "Fp_polynomial",
                        "set_coefficient( lidia_size_t )::negative index",
                        cout<<"index = "<<i<<endl; );

    lidia_size_t j, m = degree();

    if (i > m)
    {
        debug_handler_l ( "Fp_polynomial", "set_coefficient( lidia_size_t )::enlarging coefficient vector", 1 );
        set_degree(i);
        for (j = m+1; j < i; j++)
            coeff[j].assign_zero();
    }

    coeff[i].assign_one();
}

void Fp_polynomial::remove_leading_zeros()
{
    debug_handler_l ( "Fp_polynomial", "remove_leading_zeros ( void )", 1 );

	if (modulus().is_zero())
	  	lidia_error_handler( "Fp_polynomial", "remove_leading_zeros( void )::modulus = 0" );
    bigint *p;
    lidia_size_t n;

    n = c_length;
    p = coeff - 1 + n;
    while (n > 0 && (*p).is_zero() )
    {
        p--;
        n--;
    }
    c_length = n;
}

/*
bigint & Fp_polynomial::operator[] (lidia_size_t i)
{
    debug_handler_l ( "Fp_polynomial", "operator [] ( lidia_size_t )", 3 );
        if (modulus().is_zero())
                lidia_error_handler( "Fp_polynomial", "operator[]::modulus is ze
ro" );

    if ( i < 0 )
        lidia_error_handler_c ( "Fp_polynomial", "operator[]::negative index",
                    cout<<"index = "<<i<<endl; );

    if ( i >= c_length )
        lidia_error_handler_c ( "Fp_polynomial", "operator[]::index out of range
",
                    cout<<"index = "<<i<<"     c_length = "<<c_length<<endl; );
    //never: return ZERO;
    //it would discard "const" state of ZERO

    return coeff[i];
}
*/

const bigint & Fp_polynomial::operator[] (lidia_size_t i) const
{
    debug_handler_l ( "Fp_polynomial", "(const T&) operator [] ( lidia_size_t )", 3 );

    if ( i < 0 )
        lidia_error_handler_c ( "Fp_polynomial",
                                "(const bigint&) operator[]::negative index", 
                                cout<<"index = "<<i<<endl; );
    if ( i < c_length )
        return coeff[i];
    else
        return ZERO;
}


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

             assignments

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


void Fp_polynomial::assign_x()
{
	debug_handler_l ( "Fp_polynomial", "assign_x ( void )", 2 );
	if (modulus().is_zero())
	  	lidia_error_handler( "Fp_polynomial", "assign_x( void )::modulus = 0" );
	set_degree(1);
	coeff[0].assign_zero();
	coeff[1].assign_one();
}

void Fp_polynomial::assign(const Fp_polynomial &a)
{
    debug_handler_l ( "Fp_polynomial", "assign ( Fp_polynomial& )", 2 );

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

	if (&a == this)
	  	return;

    MOD = a.MOD;
    
    if (a.is_zero())
        assign_zero();
    else
    {
        set_degree(a.degree());
        for (lidia_size_t i = 0; i <= a.degree(); i++)
            coeff[i].assign( a[i] );
    }
    //this function tries to keep the memory allocated
    //by this->coeff
}

void Fp_polynomial::assign(const base_vector<bigint> &a, const bigint& p)
{
  	debug_handler_l ( "Fp_polynomial", "assign ( base_vector<bigint>&, const bigint& )", 2 );
	lidia_size_t j, deg = a.size() - 1;
	set_modulus(p);
	set_degree(deg);
	const bigint &q = modulus();
	
	for (j = 0; j <= deg; j++)
	  	Remainder(coeff[j], a[j], q);
	remove_leading_zeros();
}


void Fp_polynomial::assign(const bigint& c)
{
    debug_handler_l ( "Fp_polynomial", "assign ( bigint& )", 2 );
    if (modulus().is_zero())
	lidia_error_handler( "Fp_polynomial", "assign( bigint& )::modulus = 0" );

    bigint tmp;
    Remainder(tmp, c, modulus());
    if (tmp.is_zero())
	assign_zero();
    else
    {
	set_degree(0);
	coeff[0].assign(tmp);
    }
}


Fp_polynomial& Fp_polynomial::operator = (const Fp_polynomial &p)
{
    debug_handler_l ( "Fp_polynomial", "operator = ( Fp_polynomial& )", 2 );
    if ( this != &p )
        assign(p);
    return *this;
}


const bigint& Fp_polynomial::operator = (const bigint &c)
{
    debug_handler_l ( "Fp_polynomial", "operator = ( Fp_polynomial& )", 2 );
    assign(c);
    return c;
}


void Fp_polynomial::randomize(lidia_size_t n)
//random polynomial of degree n
{
  	debug_handler_l ( "Fp_polynomial", "randomize ( lidia_size_t )", 2 );
	if ( modulus().is_zero() )
	  	lidia_error_handler( "Fp_polynomial", "randomize ( lidia_size_t )::modulus = 0" );
	if ( n < 0 )
		lidia_error_handler( "Fp_polynomial", "randomize ( lidia_size_t )::degree < 0" );

	set_degree(n);
	for (lidia_size_t i = 0; i <= n; i++)
	  	coeff[i].assign( ::randomize(modulus()) );
	if (coeff[n].is_zero())
	    coeff[n].assign_one();
}

void randomize(Fp_polynomial& x, const bigint& p, lidia_size_t n)
{
  	debug_handler_l( "Fp_polynomial", "randomize ( Fp_polynomial&, const bigint&, lidia_size_t )", 3 );

	x.set_modulus(p);
	x.randomize(n);
}


// swap x & y (only pointers are swapped)
void swap(Fp_polynomial &x, Fp_polynomial &y)
{
  	debug_handler_l ( "Fp_polynomial", "swap ( Fp_polynomial&, Fp_polynomial& )", 2 );
	
	if (&x == &y)
	  	return;

	bigint* b_tmp;
	lidia_size_t l_tmp;

	b_tmp = x.coeff;
	x.coeff = y.coeff;
	y.coeff = b_tmp;

	l_tmp = x.c_length;
	x.c_length = y.c_length;
	y.c_length = l_tmp;

	l_tmp = x.c_alloc;
	x.c_alloc = y.c_alloc;
	y.c_alloc = l_tmp;

	swap(x.MOD, y.MOD);
}


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

               comparisons

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

bool Fp_polynomial::is_zero() const
{
    debug_handler_l ( "Fp_polynomial", "is_zero ( void )", 4 );
    return ( c_length == 0 );
}


bool Fp_polynomial::is_one() const
{
    debug_handler_l ( "Fp_polynomial", "is _one ( void )", 4 );
    return ( c_length == 1 && coeff[0].is_one() );
}

bool Fp_polynomial::is_x() const
{
    debug_handler_l ( "Fp_polynomial", "is_x ( void )", 4 );
    return ( c_length == 2 && coeff[0].is_zero() && coeff[1].is_one() );
}

bool Fp_polynomial::is_binomial() const
{
    lidia_size_t i, non_zeros = 1, n = c_length - 1;;
		//first non-zero coefficient: f(n)
    if (n < 1) return false;
    for (i = 0; i < n; i++)
	if (!coeff[i].is_zero())	
	{
	    non_zeros++;
	    if (non_zeros > 2) return false;
	}
    return (non_zeros == 2);
}

bool operator == (const Fp_polynomial &a,
            const Fp_polynomial &b)
{
    debug_handler_l ( "Fp_polynomial", "operator == ( Fp_polynomial, Fp_polynomial )", 4 );
// this code only works well if both polynomials are normalized
    lidia_size_t i, n = a.c_length;
    if ( n != b.c_length )
        return false;
    if ( a.MOD != b.MOD )
	return false;

    const bigint *ap = a.coeff,
            *bp = b.coeff;
    for (i = 0; i < n; i++)
        if (ap[i] != bp[i]) return false;

    return true;
/*
// the following code will work even if polynomials are not normalized
// for operator[] will return zero if index is out of range
    lidia_size_t max_deg = comparator<lidia_size_t>::max( a.c_length, b.c_length);
    for (lidia_size_t i = 0; i <= max_deg; i++)
        if (a[i] != b[i]) return 0;
    return 1;
*/
}

bool operator != (const Fp_polynomial &a,
            const Fp_polynomial &b)
{
    debug_handler_l ( "Fp_polynomial", "operator != ( Fp_polynomial, Fp_polynomial )", 4 );
    return !(a == b);
}


//for class factorization< Fp_polynomial > :
bool operator < (const Fp_polynomial &a, const Fp_polynomial &b)
{
    if (a.c_length != b.c_length)
	return (a.c_length < b.c_length);

    lidia_size_t i;
    for (i = a.c_length-1; i >= 0; i--)
    {
	if (a.coeff[i] < b.coeff[i]) return true;
        if (a.coeff[i] > b.coeff[i]) return false;
    }
    return false;
}

bool operator <= (const Fp_polynomial &a, const Fp_polynomial &b)
{
    if (a.c_length != b.c_length)
	return (a.c_length < b.c_length);

    lidia_size_t i;
    for (i = a.c_length-1; i >= 0; i--)
    {
	if (a.coeff[i] < b.coeff[i]) return true;
        if (a.coeff[i] > b.coeff[i]) return false;
    }
    return true;
}




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

               input and output

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

	/*
	** I/O format:  "[a_0 a_1 ... a_n] mod p"
	**   represents the polynomial a_0 + a_1*x + ... + a_n*x^n over Z/pZ.
	**   for operator << and >>, write, read.
	** pretty_print prints "x^2-x+3 mod 5" instead of "[3 -1 1] mod 5"
	*/

istream & operator >> (istream & s, Fp_polynomial &a)
{
    debug_handler_l ( "Fp_polynomial", "operator >> ( istream&, Fp_polynomial& )", 3);
    a.read(s);
    return s;
}

ostream & operator << (ostream & s, const Fp_polynomial &a)
{
    debug_handler_l ( "Fp_polynomial", "operator << ( ostream&, const Fp_polynomial&)", 3 );
    a.pretty_print(s);
    return s;
}


void Fp_polynomial::read(istream & s)
{
    debug_handler_l ( "Fp_polynomial", "read( istream& )", 2 );
    bigint p;
    lidia_size_t n, i;
    char c, cm,co,cd;

    s >> ws >> c;
    
	if (c != '[')
	{
	  	s.putback(c);
  		pretty_read(s);
		return;
	}
    n = 0;
    s >> c;
    while (c != ']')
    {
        s.putback(c);
        if (n % 20 == 0)     // = INPUT_BLOCK_SIZE
        {
            debug_handler_l( "Fp_polynomial", "read( istream& )::enlarging input vector", 1 );
			c_length = n;
            set_max_degree(n+20);
        }
        n++;
        s >> coeff[n-1];
        s >> c;
    }
    c_length = n;
	s >> cm >> co >> cd;
    if (cm!='m' || co!='o' || cd!='d')
        lidia_error_handler( "Fp_polynomial", "read( istream& )::'mod' expected" );
    s >> p;
    MOD.set(p);

    for (i = 0; i < c_length/2; i++){
      Remainder(coeff[c_length-i-1], coeff[c_length-i-1], p);
      swap(coeff[i], coeff[c_length-i-1]);
    }
    for (; i < c_length; i++){
      Remainder(coeff[i], coeff[i], p);
    }
		
    remove_leading_zeros();
}

void Fp_polynomial::write(ostream & s) const
{
    debug_handler_l ( "Fp_polynomial", "write( ostream& )", 2 );
	
	s << "[";
    for (lidia_size_t i = degree(); i >= 0; i--)
    {
        s << coeff[i];
        if (i != 0 )
            s<<" ";
    }
    s << "] mod "<<modulus();
};


void Fp_polynomial::pretty_print(ostream &os) const
{ //written by Damian Weber
//assumes lead_coeff != 0, all coeffs must be > 0

  	debug_handler_l ( "Fp_polynomial", "pretty_print( ostream& )", 2 );

	lidia_size_t j, deg = c_length-1;
	
	for (j = deg; j >= 0; j--)
	{
		if (coeff[j]!=0)
		{
			if ( coeff[j]>0 && j<deg )
				os << " + ";
			if (j==0 || coeff[j]!=1) 
			{
				os << coeff[j];
				if (j>0) 
				  	os << "*";
			}
			if (j>0) 
				os << "x";
			if (j>1) 
			  	os << "^" << j;
		}
	}
	os << " mod "<<modulus();
}


void Fp_polynomial::pretty_read(istream &s)
{
  // This function reads a univariate polynomial in any variable.
  // input format : a_n*x^n+ ... + a_1*x + a_0 mod p
  // e.g. :   3*x^2 + 4*x - 1 mod 17
  // Monomials need not be sorted, and powers of x may even appear 
  // repeated, '*' may be omitted and coefficients may follow the variable:
  //        -4 + 8x^5 + 2 - x^2 3 + x^5 + x^5*17 mod 5
  // Note however, that the routine will work faster, if the leading monomial
  // is read first. 
  // All coefficients will be reduced mod p.

  debug_handler_l(DM_BP,"in member-function "
                  "istream & read_verbose(istream &)", DV_BP + 6);

  register lidia_size_t n = 0;
  lidia_size_t sz;
  char c;

  set_degree(8);
  for(; n < c_alloc; n++)
    coeff[n] = 0;

  char variable = 0;
  bigint coeff_tmp(1);
  bigint tmp;

  // Read a monomial, i.e. "x^k" or "- x^k"
  // or "a*x^k" or "a x^k" or "x^k*a" or "x^k a"

  do{
    c = s.get();
  } while (isspace(c) && c != '\n');
  while (c!='\n' && c != EOF && s.good()){
    sz = 0;             // Assume we read coeffizient of x^0;
    if (c=='+'){
      coeff_tmp = 1;
      do{
        c = s.get();
      } while (isspace(c) && c != '\n');
    }
    if (c=='-'){
      coeff_tmp = -1;
      do{
        c = s.get();
      } while (isspace(c) && c != '\n');
    }
    if (c>='0' && c<='9' || c == '('){
      s.putback(c);
      s >> tmp;
      coeff_tmp *= tmp;
      do{
        c = s.get();
      } while (isspace(c) && c != '\n');
      if (c == '*'){
        do{
          c = s.get();
        } while (isspace(c) && c != '\n');
	if (c == 'm'){
	  c = s.get();
	  if (c == 'o')
	    lidia_error_handler("Fp_polynomial",
				"pretty_read ( ... )::wrong input format");
	  else s.putback(c);
	}
      }
    }
    if (c == 'm'){
      c = s.get();
      if (c == 'o'){
	c = s.get();
	if (c != 'd')
	  lidia_error_handler("Fp_polynomial", 
			      "pretty_read ( ... ):: 'mod' expected");
	c = '\n';
      }
      else s.putback(c);
    }
    if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')){
      if (variable == 0)
        variable = c;
      else if (variable != c)
        lidia_error_handler_c("Fp_polynomial", "pretty_read ( ... )::"
                              "The given string is not recognized to be"
			      " a univariate polynomial",
                              cout << "Variable name seemed to be "<<variable;
                              cout << " and now you used "<<c<<"."<<endl);
      do{
        c = s.get();
      } while (isspace(c) && c != '\n');

      if (c != '^') sz = 1;
      else {
        s >> sz;
        do {
          c = s.get();
        } while (isspace(c) && c != '\n');
      }
      if (c=='*'){
        s >> tmp;
        coeff_tmp *= tmp;
        do{
          c = s.get();
        } while (isspace(c) && c != '\n');
      }

      if (c>='0' && c<='9'|| c == '('){
        s.putback(c);
        s >> tmp;
        coeff_tmp *= tmp;
        do{
          c = s.get();
        } while (isspace(c) && c != '\n');
      }
    }
    if (c == 'm'){
      c = s.get();
      if (c != 'o')
	lidia_error_handler("Fp_polynomial", 
			    "pretty_read ( ... ):: 'mod' expected");
      c = s.get();
      if (c != 'd')
	lidia_error_handler("Fp_polynomial", 
			    "pretty_read ( ... ):: 'mod' expected");

      c = '\n';
    }
    if (c!='+' && c!='-' && c!='\n') {
      // No next monomial, so assume end of input is reached
      s.putback(c);
      c = '\n';	// set c to end--marker
    }
    if (sz >= c_length){
      set_degree(sz);	// Additional coefficients are initialised with zero!!
    }
    coeff[sz]+=coeff_tmp;
  }

  bigint p;
  s >> p;
  MOD.set(p);

  for (n = 0; n < c_length; n++)
    Remainder(coeff[n], coeff[n], p);
  remove_leading_zeros();
}
