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


#ifndef LIDIA_GF_POLYNOMIAL_H
#define LIDIA_GF_POLYNOMIAL_H

/**
 ** This include file supports polynomial operations over the
 ** class 'gf_p_element'
 ** It is based on Stefan Neis' implementation of polynomials over
 ** arbitrary types.
 ** Arithmetic (esp. modular arithmetic) is very similar to 
 ** arithmetic in the class Fp_polynomial.
 **					Thomas Pfahler
 **/
 


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


  /**
   **   SPECIAL INSTATIATION
   **/

//
// typedef polynomial< gf_p_element > gf_polynomial
//

class polynomial< gf_p_element >
{
 private:
    //static const gf_p_base& common_field(const gf_p_base&, const gf_p_base&);
    static const gf_p_base& common_field(const field_ref&, const field_ref&);
	//might return a common superfield of a and b
	//in this pre-version, an error is raised if a!=b and
	//neither a nor b equal NULL

    void check_coefficients();
	//raises an error if ffield is not defined
	//or if coeff[i].base() is not this->base() for all 
	//i=0,..,degree()

    void build_frame() const
	{ field_ref::enter_frame(ffield.base()); }
    void build_frame(const field_ref& f)
	{ ffield.assign(f); field_ref::enter_frame(f.base()); }
    void delete_frame() const
	{ field_ref::exit_frame(); }
    //these functions build and delete a frame which indicates
    //over which field the following operations are done;
    //this is essential for many operations of class gf_p_element
    //e.g. empty constructors ...

    field_polynomial< gf_p_element > pol;
    field_ref ffield;
	//############

 public:

 /**
  ** constructors and destructor
  **/
  
    polynomial () : 
	pol(), ffield()
    { }
	
    polynomial (const gf_p_element &x)
    {  
	build_frame(x.base());  
	pol.assign(x);  
	delete_frame();
    }

    polynomial (const polynomial< gf_p_element > &p)
    {  
	build_frame(p.ffield);  
	pol.assign(p.pol);  
	delete_frame();
    }
	
    polynomial (const gf_p_base &b)
    {    this->assign_zero(b);   }

    polynomial(const gf_p_element *v, lidia_size_t d) :
	pol(v, d)
    {	check_coefficients();    }

    polynomial(const base_vector< gf_p_element > &v) :
	pol(v)
    {	check_coefficients();    }

    ~polynomial()
    { }


  /**
   ** inline friend functions
   **/

  /**  comparisons  **/

  friend inline bool operator == (const polynomial< gf_p_element > &a,
				  const polynomial< gf_p_element > &b)
  { return a.ffield == b.ffield  &&  a.pol == b.pol; }

  friend inline bool operator != (const polynomial< gf_p_element > &a,
				  const polynomial< gf_p_element > &b)
  { return a.ffield != b.ffield  ||  a.pol != b.pol; } 

  gf_p_element lead_coeff() const;
 
  gf_p_element const_term() const;

  friend void swap(polynomial< gf_p_element > &a, polynomial< gf_p_element > &b);
  
  /**
   ** member functions
   **/

  void remove_leading_zeros();

  int set_data ( const gf_p_element * d, lidia_size_t l );

  gf_p_element* get_data () const;

  const gf_p_base &base() const
  { return ffield.base(); }

  void set_base(const gf_p_base &PB);

  lidia_size_t degree() const
  { return pol.degree(); }

  void set_degree(lidia_size_t d)
  { build_frame(); pol.set_degree(d); delete_frame(); }

  bool is_monic() const;

  bool is_zero() const
  { return pol.is_zero(); }

  bool is_one() const;

  bool is_x() const;

  /**
   ** assignment
   **/

  void assign(const gf_p_element &a);

  friend void assign(polynomial< gf_p_element > & a, const gf_p_element & b);

  const gf_p_element & operator = (const gf_p_element &a);

  void assign(const polynomial< gf_p_element > &a)
  { build_frame(a.ffield); pol.assign(a.pol); delete_frame(); }

  friend void assign(polynomial< gf_p_element > & a,
		     const polynomial< gf_p_element > & b);

  polynomial< gf_p_element > & operator = (const polynomial< gf_p_element > &a)
  { this->assign(a); return *this; }

  void assign_zero()
  {
    if (ffield.is_undefined()) lidia_error_handler("polynomial< gf_p_element >", "assign_zero() :: field unknown");
    pol.assign_zero();
  }

  void assign_one()
  {
    if (ffield.is_undefined()) lidia_error_handler("polynomial< gf_p_element >", "assign_one() :: field unknown");
    build_frame();
    pol.assign_one();
    delete_frame();
  }
  
  void assign_x()
  {
    if (ffield.is_undefined()) lidia_error_handler("polynomial< gf_p_element >", "assign_x() :: field unknown");
    build_frame();
    pol.assign_x();
    delete_frame();
  }

  void assign_zero(const gf_p_base &b);

  void assign_one(const gf_p_base &b);

  void assign_x(const gf_p_base &b);


  /**
   ** arithmetic procedures
   **/

  friend void negate(polynomial< gf_p_element > & c,
		     const polynomial< gf_p_element > &a)
  { c.build_frame(a.ffield); negate(c.pol, a.pol); c.delete_frame(); }

  friend void add(polynomial< gf_p_element > & c,
		  const polynomial< gf_p_element > & a,
		  const polynomial< gf_p_element > & b)
  { c.build_frame(common_field( a.ffield, b.ffield ));
    add(c.pol, a.pol, b.pol);
    c.delete_frame(); }
    
  friend void add(polynomial< gf_p_element > & c,
		  const polynomial< gf_p_element > & a, const gf_p_element & b);
    
  friend void add(polynomial< gf_p_element > & c,
		  const gf_p_element & b, const polynomial< gf_p_element > & a);


  friend void subtract(polynomial< gf_p_element > & c,
		       const polynomial< gf_p_element > & a,
		       const polynomial< gf_p_element > & b)
  { c.build_frame(common_field(a.ffield,b.ffield));
    subtract(c.pol, a.pol, b.pol);
    c.delete_frame(); }
    
  friend void subtract(polynomial< gf_p_element > & c,
	       const polynomial< gf_p_element > & a, const gf_p_element & b);
  
  friend void subtract(polynomial< gf_p_element > & c,
	       const gf_p_element & b, const polynomial< gf_p_element > & a);


  friend void multiply(polynomial< gf_p_element > & c,
		       const polynomial< gf_p_element > & a,
		       const polynomial< gf_p_element > & b);
    
  friend void multiply(polynomial< gf_p_element > & c,
	       const polynomial< gf_p_element > & a, const gf_p_element & b);
    
  friend void multiply(polynomial< gf_p_element > & c,
	       const gf_p_element & b, const polynomial< gf_p_element > & a);

  friend void square(polynomial< gf_p_element > & c,
		    const polynomial< gf_p_element > & a);

  friend void power(polynomial< gf_p_element > & c,
		    const polynomial< gf_p_element > & a, const bigint & b);



  /**
   ** operator overloading
   **/

  inline gf_p_element & operator[] (lidia_size_t i) const
  { return pol[i]; }

  gf_p_element operator() (const gf_p_element & value) const;

  friend
  polynomial< gf_p_element > operator - (const polynomial< gf_p_element > &a);

  friend
  polynomial< gf_p_element > operator + (const polynomial< gf_p_element > &a,
				    const polynomial< gf_p_element > &b);

  friend
  polynomial< gf_p_element > operator + (const polynomial< gf_p_element > & a,
				    const gf_p_element& b);

  friend
  polynomial< gf_p_element > operator + (const gf_p_element & b,
				    const polynomial< gf_p_element > & a);


  friend
  polynomial< gf_p_element > operator - (const polynomial< gf_p_element > &a,
				    const polynomial< gf_p_element > &b);

  friend
  polynomial< gf_p_element > operator - (const polynomial< gf_p_element > &a,
				    const gf_p_element &b);

  friend
  polynomial< gf_p_element > operator - (const gf_p_element &a,
				    const polynomial< gf_p_element > &b);

  friend
  polynomial< gf_p_element > operator * (const polynomial< gf_p_element > &a,
				    const polynomial< gf_p_element > &b);

  friend
  polynomial< gf_p_element > operator * (const polynomial< gf_p_element > &a,
				    const gf_p_element &b);

  friend
  polynomial< gf_p_element > operator * (const gf_p_element &b,
				    const polynomial< gf_p_element > &a);

  polynomial< gf_p_element >& operator += (const polynomial< gf_p_element > &a);

  polynomial< gf_p_element >& operator += (const gf_p_element &a);

  polynomial< gf_p_element >& operator -= (const polynomial< gf_p_element > &a);

  polynomial< gf_p_element >& operator -= (const gf_p_element &a);

  polynomial< gf_p_element >& operator *= (const polynomial< gf_p_element > &a);

  polynomial< gf_p_element >& operator *= (const gf_p_element &a);

  /**
   ** functions
   **/
  friend void derivative(polynomial< gf_p_element > &,
			 const polynomial< gf_p_element > &);
  friend
  polynomial< gf_p_element > derivative(const polynomial< gf_p_element > & a);

  friend void integral(polynomial< gf_p_element > &,
		       const polynomial< gf_p_element > &);	       
  friend
  polynomial< gf_p_element > integral(const polynomial< gf_p_element > & a);

  
  friend polynomial< gf_p_element > randomize(const gf_p_base &b, lidia_size_t n);


  /**
   ** input / output
   **/

  friend istream & operator >> (istream &is, polynomial< gf_p_element > &a);
  
  istream & read_verbose(istream &is);
    
  friend ostream & operator << (ostream &os, const polynomial< gf_p_element > &a);


  /**
   ** Division and related stuff
   **/

  friend void div_rem(polynomial< gf_p_element > & q, polynomial< gf_p_element > & r,
		      const polynomial< gf_p_element > & a,
		      const polynomial< gf_p_element > & b);

  friend
  void divide(polynomial< gf_p_element > & c,
	     const polynomial< gf_p_element > & a, const gf_p_element & b)
  { c.build_frame(common_field(a.ffield, b.base()));
    divide(c.pol, a.pol, b);
    c.delete_frame(); }

  friend
  void divide(polynomial< gf_p_element > & q, 
	      const polynomial< gf_p_element > & a,
	      const polynomial< gf_p_element > & b);

  friend
  void remainder(polynomial< gf_p_element > & r, 
		 const polynomial< gf_p_element > & a,
		 const polynomial< gf_p_element > & b);

  friend
  polynomial< gf_p_element > operator / (const polynomial< gf_p_element > &a,
				     const polynomial< gf_p_element > &b);

  friend
  polynomial< gf_p_element > operator / (const polynomial< gf_p_element > &a,
				     const gf_p_element & b);

  friend
  polynomial< gf_p_element > operator % (const polynomial< gf_p_element > &a,
				     const polynomial< gf_p_element > &b);

  polynomial< gf_p_element >& operator /= (const polynomial< gf_p_element > &a);

  polynomial< gf_p_element >& operator /= (const gf_p_element &a);

  polynomial< gf_p_element >& operator %= (const polynomial< gf_p_element > &a);



  friend polynomial< gf_p_element > gcd(const polynomial< gf_p_element > &aa,
			      const polynomial< gf_p_element > &bb);

  friend void gcd(polynomial< gf_p_element > &d,
    const polynomial< gf_p_element > &aa, const polynomial< gf_p_element > &bb);
    
  friend polynomial< gf_p_element > xgcd(polynomial< gf_p_element > &x,
			       polynomial< gf_p_element > &y,
			       const polynomial< gf_p_element > &aa,
			       const polynomial< gf_p_element > &bb);
  
  friend void 
  xgcd(polynomial< gf_p_element > &d, polynomial< gf_p_element > &x,
       polynomial< gf_p_element > &y, const polynomial< gf_p_element > &aa,
       const polynomial< gf_p_element > &bb);


  /**
  **  modular arithmetic without pre-conditioning
  **/
	    
  friend void multiply_mod(polynomial< gf_p_element > & x,
    const polynomial< gf_p_element > & a, const polynomial< gf_p_element > & b,
    const polynomial< gf_p_element > & f);
    
  friend void square_mod(polynomial< gf_p_element > & x,
    const polynomial< gf_p_element > & a, const polynomial< gf_p_element > & f);
    
  friend void multiply_by_x_mod(polynomial< gf_p_element > & h,
    const polynomial< gf_p_element > & a, const polynomial< gf_p_element > & f);
    
  friend void invert_mod(polynomial< gf_p_element > & x,
    const polynomial< gf_p_element > & a, const polynomial< gf_p_element > & f);
    
  friend bool invert_mod_status(polynomial< gf_p_element > & x,
    const polynomial< gf_p_element > & a, const polynomial< gf_p_element > & f);
    
  friend void power_mod(polynomial< gf_p_element > & h,
    const polynomial< gf_p_element > & g, const bigint & e,
    const polynomial< gf_p_element > & f);
    
  friend void power_x_mod(polynomial< gf_p_element > & h,
    const bigint & e, const polynomial< gf_p_element > & f);
    
  friend void power_x_plus_a_mod(polynomial< gf_p_element > & h,
    const gf_p_element & a, const bigint & e,
    const polynomial< gf_p_element > & f);
    
  friend void cyclic_reduce(polynomial< gf_p_element > & x,
    const polynomial< gf_p_element > & a, lidia_size_t m);


   /**
   **  'plain' arithmetic (slow)
   **/
  friend void plain_power(polynomial< gf_p_element > & c,
    const polynomial< gf_p_element > & a, const bigint & b);
  friend void plain_gcd(polynomial< gf_p_element > &d,
    const polynomial< gf_p_element > &aa, const polynomial< gf_p_element > &bb);
  friend void plain_multiply(polynomial< gf_p_element > &c,
    const polynomial< gf_p_element > &a, const polynomial< gf_p_element > &b);
  friend void plain_square(polynomial< gf_p_element > & c,
    const polynomial< gf_p_element > & a);
  friend void plain_div_rem(polynomial< gf_p_element > &q,
    polynomial< gf_p_element > &r, const polynomial< gf_p_element > &a,
    const polynomial< gf_p_element > &b);
  friend void plain_divide(polynomial< gf_p_element > &q,
    const polynomial< gf_p_element > &a, const polynomial< gf_p_element > &b);
  friend void plain_remainder(polynomial< gf_p_element > &r,
    const polynomial< gf_p_element > &a, const polynomial< gf_p_element > &b);
  
  /**
  **  'fast'  arithmetic (Kronecker substitution)
  **/
  friend void fast_multiply(polynomial< gf_p_element >& c,
    const polynomial< gf_p_element >& a, const polynomial< gf_p_element >& b);
  friend void fast_div_rem(polynomial< gf_p_element >& q,
    polynomial< gf_p_element >& r, const polynomial< gf_p_element >& a,
    const polynomial< gf_p_element >& b);
  friend void invert(polynomial< gf_p_element >& x,
    const polynomial< gf_p_element >& a, lidia_size_t m);
  friend void copy_reverse(polynomial< gf_p_element >& x,
    const polynomial< gf_p_element >& a, lidia_size_t lo, lidia_size_t hi);

  /**
  **  modular arithmetic with pre-conditioning (class gf_poly_modulus)
  **/
  friend class gf_poly_modulus;
  friend void remainder(polynomial< gf_p_element > &c,
    const polynomial< gf_p_element > &a, const gf_poly_modulus &P);
  friend void multiply(polynomial< gf_p_element > &c,
    const polynomial< gf_p_element > &a, const polynomial< gf_p_element > &b,
    const gf_poly_modulus &P);
  friend void square(polynomial< gf_p_element > &c,
    const polynomial< gf_p_element > &a, const gf_poly_modulus &P);
  friend void invert(polynomial< gf_p_element > &c,
    const polynomial< gf_p_element > &a, const gf_poly_modulus &P);
  friend void power(polynomial< gf_p_element > &c,
    const polynomial< gf_p_element > &a, const bigint &e,
    const gf_poly_modulus &P);
  friend void power_x(polynomial< gf_p_element > &c, const bigint &e,
    const gf_poly_modulus &P);
  friend void power_x_plus_a(polynomial< gf_p_element > &c,
    const gf_p_element &a, const bigint &e, const gf_poly_modulus &P);


//  friend class gf_poly_multiplier;
//  friend void multiply(polynomial< gf_p_element > &x,
//    const polynomial< gf_p_element >& a, gf_poly_multiplier& B,
//    gf_poly_modulus & F);


  /**
  **  stuff for factoring algorithms
  **/
  friend void random_basis_elt2(polynomial< gf_p_element >& g,
    const base_vector < sdigit > &D, Fp_polynomial** M,
				const gf_p_base & pb); // MM
  friend void build_matrix(gf_p_element **&M, lidia_size_t n,
    const polynomial< gf_p_element > & g, const polynomial< gf_p_element > & f);


  friend bool checked_min_poly(polynomial< gf_p_element >& h,
    const polynomial< gf_p_element >& g, lidia_size_t r,
    const gf_poly_modulus& F);

  friend void trace_map(polynomial< gf_p_element >& w,
    const polynomial< gf_p_element >& a, lidia_size_t d,
    const gf_poly_modulus &f, const polynomial< gf_p_element >& b);
 
  friend void find_roots(class base_vector<gf_p_element> &,
    const class polynomial<gf_p_element> &);
  
  
};	/*   end class polynomial< gf_p_element >   */


typedef polynomial< gf_p_element > gf_polynomial;

  //for class single_factor< gf_polynomial >
bool operator < (const polynomial< gf_p_element > &a, 
		    const polynomial< gf_p_element > &b);
bool operator <= (const polynomial< gf_p_element > &a,
		    const polynomial< gf_p_element > &b);



base_vector< gf_p_element > find_roots(const gf_polynomial &f);


class gf_poly_modulus
{
    gf_polynomial F_plain;
    Fp_polynomial F, F_recip;

 public:
    gf_poly_modulus() { }
    gf_poly_modulus(const gf_poly_modulus &p);
    gf_poly_modulus(const gf_polynomial &f);

    void build(const gf_polynomial &f);
    
    const gf_polynomial& modulus() const
    { return F_plain; }

    void rem21(gf_polynomial &c, const gf_polynomial &a) const;
    friend void remainder(gf_polynomial &c, const gf_polynomial &a,
			const gf_poly_modulus &P);
    friend void multiply(gf_polynomial &c, const gf_polynomial &a,
			const gf_polynomial &b, const gf_poly_modulus &P);
    friend void square(gf_polynomial &c, const gf_polynomial &a,
			const gf_poly_modulus &P);
    friend void invert(gf_polynomial &c, const gf_polynomial &a,
			const gf_poly_modulus &P);
    friend void power(gf_polynomial &c, const gf_polynomial &a,
			const bigint &e, const gf_poly_modulus &P);
    friend void power_x(gf_polynomial &c, const bigint &e,
			const gf_poly_modulus &P);
    friend void power_x_plus_a(gf_polynomial &c, const gf_p_element &a,
			const bigint &e, const gf_poly_modulus &P);

//    friend class gf_poly_multiplier;
//    friend void multiply(gf_polynomial &x, const gf_polynomial& a,
//	    gf_poly_multiplier& B, gf_poly_modulus & F);
};



#if 0
/*
The idea of gf_poly_multiplier (the analogon to 'poly_multiplier' for
Fp_polynomial) does not speed up the multiplication significantly.
Time is spent (with or without this class) in three multiplications of
Fp_polynomial's. Conversions (see Kronecker substitution) do hardly cost
anything - and this is the only thing we could save.
Bad luck.
*/
class gf_poly_multiplier
{
    gf_polynomial B_plain;
    Fp_polynomial B, B_div_F;
    const gf_poly_modulus* mod;
    
 public:
    gf_poly_multiplier();
    gf_poly_multiplier(const gf_poly_multiplier& PM);
    gf_poly_multiplier(const gf_polynomial& b, const gf_poly_modulus& F);
    ~gf_poly_multiplier();

    void build(const gf_polynomial& b, const gf_poly_modulus& F);
    const gf_polynomial& polynomial() const
    { return B_plain; }

    friend void multiply(gf_polynomial &x, const gf_polynomial& a,
	    gf_poly_multiplier& B, gf_poly_modulus & F);
};
#endif


void to_Kronecker(Fp_polynomial &g, const gf_polynomial &f, lidia_size_t lo, lidia_size_t hi);
void from_Kronecker(gf_polynomial &f, const Fp_polynomial &g, lidia_size_t lo, lidia_size_t hi);



class gf_poly_argument
{
    gf_polynomial *vec;
    lidia_size_t len;
    
    void inner_prod(gf_polynomial &x, const gf_polynomial &g,
		    lidia_size_t lo, lidia_size_t hi) const;

 public:
    gf_poly_argument();
    gf_poly_argument(const gf_poly_argument &x);
    ~gf_poly_argument();
    
    void build(const gf_polynomial &h, const gf_poly_modulus &F, 
		lidia_size_t m);
    //computes and stores h, h^2, ..., h^m mod f

    void compose(gf_polynomial &x, const gf_polynomial &g, 
		const gf_poly_modulus &F) const;
    //x = g(h) mod F
};
    

#endif
