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

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


const gf_p_base& polynomial<gf_p_element>::common_field(const field_ref &a, const field_ref &b)
//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
{
    if (a.is_undefined() && b.is_undefined())
    {
	cout<<"common_field::WARNING (both arguments are NULL)"<<endl;
	return a.base();
    }
    if (a != b)
	lidia_error_handler("polynomial<gf_p_base>","common_field::arguments are different fields");
    return a.base();
}


void polynomial< gf_p_element >::check_coefficients()
{
    lidia_size_t i, d = pol.degree();
    if (ffield.is_undefined())
	lidia_error_handler("gf_p_element","check_coefficients()::don't know over which field");

    const gf_p_base *b = &(ffield.base());
    for (i = 0; i <=d; i++)
    {
	if (&(pol[i].base()) != b)
	    lidia_error_handler("gf_p_element","check_coefficients()::coefficients lie in different fields");
    }
}


int polynomial< gf_p_element >::set_data( const gf_p_element * d, lidia_size_t l )
{
    int r = pol.set_data(d, l);
    check_coefficients();
    return r;
}


gf_p_element* polynomial< gf_p_element >::get_data() const
{
    if (ffield.is_undefined())
	lidia_error_handler("polynomial<gf_p_element>","get_data::no field given for this polynomial - don't know which 'zero' to return" );
    build_frame(); 
    gf_p_element *d = pol.get_data();
    delete_frame();
    return d;
}


gf_p_element polynomial< gf_p_element >::lead_coeff() const
{
    if (ffield.is_undefined())
	lidia_error_handler("polynomial<gf_p_element>","lead_coeff::no field given for this polynomial - don't know which 'zero' to return" );
    build_frame();
    gf_p_element tmp(pol.lead_coeff());
    delete_frame();
    return tmp;
}

gf_p_element polynomial< gf_p_element >::const_term() const
{
    if (ffield.is_undefined())
	lidia_error_handler("polynomial<gf_p_element>","const_term::deg < 0 and no field given for this polynomial - don't know which 'zero' to return" );
    build_frame();
    gf_p_element tmp(pol.const_term());
    delete_frame();
    return tmp;
}

void swap(polynomial< gf_p_element > &a, polynomial< gf_p_element > &b)
{
    field_polynomial< gf_p_element > tmp(a.pol);
    a.pol.assign(b.pol);
    b.pol.assign(tmp);
    a.ffield.swap(b.ffield);
}

void polynomial< gf_p_element >::remove_leading_zeros()
{
    build_frame();
    lidia_size_t d = degree();
    while (d >= 0  &&  pol[d].is_zero())
	d--;
    if (d != degree()) set_degree(d);
    delete_frame();
}

void polynomial< gf_p_element >::set_base(const gf_p_base &PB)
{
    this->assign_zero(PB);
}

bool polynomial< gf_p_element >::is_monic() const
{
    if (is_zero()) return false;
    return (*this)[degree()].is_one();
}

bool polynomial< gf_p_element >::is_one() const
{
    build_frame();
    bool ret = pol.is_one();
    delete_frame();
    return ret;
}
  
bool polynomial< gf_p_element >::is_x() const  
{
    build_frame();
    bool ret = pol.is_x();
    delete_frame();
    return ret;
}
    

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

void assign(polynomial< gf_p_element > & a, const gf_p_element & b)
    { a.assign(b);}

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

void assign(polynomial< gf_p_element > & a,
		const polynomial< gf_p_element > & b)
    { a.assign(b); }

void polynomial< gf_p_element >::assign_zero(const gf_p_base &b)
{
    build_frame(b);
    pol.assign_zero();
    delete_frame();
}

void polynomial< gf_p_element >::assign_one(const gf_p_base &b)
{
    build_frame(b);
    pol.assign_one();
    delete_frame();
}

void polynomial< gf_p_element >::assign_x(const gf_p_base &b)
{
    build_frame(b);
    pol.assign_x();
    delete_frame();
}

//for class single_factor< gf_polynomial >
bool operator < (const polynomial< gf_p_element > &a,
                        const polynomial< gf_p_element > &b)
{
    lidia_size_t i, n = a.degree(), m = b.degree();
    if (n != m)
	return (n < m);
    for (i = n; i >= 0; i--)
    {
	if (a[i].polynomial_rep() < b[i].polynomial_rep())  return true;
	if (!(a[i].polynomial_rep() <= b[i].polynomial_rep()))  return false;
    }
    return false;
}
    
bool operator <= (const polynomial< gf_p_element > &a,
                        const polynomial< gf_p_element > &b)
{
    lidia_size_t i, n = a.degree(), m = b.degree();
    if (n != m)
	return (n < m);
    for (i = n; i >= 0; i--)
    {
	if (a[i].polynomial_rep() < b[i].polynomial_rep())  return true;
	if (!(a[i].polynomial_rep() <= b[i].polynomial_rep()))  return false;
    }
    return true;
}



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

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

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

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

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

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


gf_p_element 
polynomial< gf_p_element >::operator() (const gf_p_element & value) const
{
    build_frame();
    gf_p_element ret = pol(value);
    delete_frame();
    return ret;
}

polynomial< gf_p_element > operator - (const polynomial< gf_p_element > &a)
    { polynomial< gf_p_element > c; negate(c,a); return c;}

polynomial< gf_p_element > operator + (const polynomial< gf_p_element > &a,
                                    const polynomial< gf_p_element > &b)
    { polynomial< gf_p_element > c; add(c,a,b); return c;}

polynomial< gf_p_element > operator + (const polynomial< gf_p_element > & a,
                                    const gf_p_element& b)
    { polynomial< gf_p_element > c; add(c,a,b); return c;}

polynomial< gf_p_element > operator + (const gf_p_element & b,
                                    const polynomial< gf_p_element > & a)
    { polynomial< gf_p_element > c; add(c,a,b); return c;}

polynomial< gf_p_element > operator - (const polynomial< gf_p_element > &a,
                                    const polynomial< gf_p_element > &b)
    { polynomial< gf_p_element > c; subtract(c,a,b); return c;}

polynomial< gf_p_element > operator - (const polynomial< gf_p_element > &a,
                                    const gf_p_element &b)
    { polynomial< gf_p_element > c; subtract(c,a,b); return c;}

polynomial< gf_p_element > operator - (const gf_p_element &a,
                                    const polynomial< gf_p_element > &b)
    { polynomial< gf_p_element > c; subtract(c,a,b); return c;}

polynomial< gf_p_element > operator * (const polynomial< gf_p_element > &a,
                                    const polynomial< gf_p_element > &b)
    { polynomial< gf_p_element > c; multiply(c,a,b); return c;}

polynomial< gf_p_element > operator * (const polynomial< gf_p_element > &a,
                                    const gf_p_element &b)
    { polynomial< gf_p_element > c; multiply(c,a,b); return c;}

polynomial< gf_p_element > operator * (const gf_p_element &b,
                                    const polynomial< gf_p_element > &a)
    { polynomial< gf_p_element > c; multiply(c,a,b); return c;}

polynomial< gf_p_element > & 
polynomial< gf_p_element >::operator += (const polynomial< gf_p_element > &a)
    { add(*this, *this, a); return *this;}

polynomial< gf_p_element > & 
polynomial< gf_p_element >::operator += (const gf_p_element &a)
    { add(*this, *this, a); return *this;}

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

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

polynomial< gf_p_element > & 
polynomial< gf_p_element >::operator *= (const polynomial< gf_p_element > &a)
    { multiply(*this, *this, a); return *this;}

polynomial< gf_p_element > & 
polynomial< gf_p_element >::operator *= (const gf_p_element &a)
    { multiply(*this, *this, a); return *this;}



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

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

polynomial< gf_p_element > integral(const polynomial< gf_p_element > & a)
    {polynomial< gf_p_element > c; integral(c,a); return c;}

polynomial< gf_p_element > randomize(const gf_p_base &b, lidia_size_t n)
{
    polynomial< gf_p_element > f;
    f.build_frame(b);
    f.set_degree(n);
    for (lidia_size_t i = 0; i < n; i++)
	f[i].randomize();
    do
	f[n].randomize();
    while (f[n].is_zero());
    
    f.delete_frame();
    return f;
}
  

istream & operator >> (istream &is, polynomial< gf_p_element > &a)
{
    if (a.ffield.is_undefined())
	lidia_error_handler("polynomial< gf_p_element >","operator >>::polynomial must be assigned to a field before any input");
    a.build_frame();
    is >> a.pol;
    a.delete_frame();
    a.remove_leading_zeros();
    a.check_coefficients();
    return is;
}

istream & polynomial< gf_p_element >::read_verbose(istream &is)
{
    if (ffield.is_undefined())
	lidia_error_handler("polynomial< gf_p_element >","read_verbose(...)::polynomial must be assigned to a field before any input");
    build_frame();
    pol.read_verbose(is);
    delete_frame();
    check_coefficients();
    remove_leading_zeros();
    return is;
}

ostream &operator << (ostream &os, const polynomial< gf_p_element > &a)
    { a.build_frame(); os << a.pol; a.delete_frame(); return os; }

polynomial< gf_p_element > operator / (const polynomial< gf_p_element > &a,
                                     const polynomial< gf_p_element > &b)
    {polynomial< gf_p_element > q, r; div_rem(q, r, a, b); return q;}

polynomial< gf_p_element > operator / (const polynomial< gf_p_element > &a,
                                     const gf_p_element & b)
    {polynomial< gf_p_element > q; divide(q, a, b); return q;}

polynomial< gf_p_element > operator % (const polynomial< gf_p_element > &a,
                                     const polynomial< gf_p_element > &b)
    {polynomial< gf_p_element > q, r; div_rem(q, r, a, b); return r;}

polynomial< gf_p_element > & 
polynomial< gf_p_element >::operator /= (const polynomial< gf_p_element > &a)
    {polynomial< gf_p_element > r; div_rem(*this, r, *this, a); return *this;}

polynomial< gf_p_element > & 
polynomial< gf_p_element >::operator /= (const gf_p_element &a)
    { divide(*this, *this, a); return *this;}

polynomial< gf_p_element > & 
polynomial< gf_p_element >::operator %= (const polynomial< gf_p_element > &a)
    {polynomial< gf_p_element > q; div_rem(q, *this, *this, a); return *this;}

polynomial< gf_p_element > gcd(const polynomial< gf_p_element > &aa,
                              const polynomial< gf_p_element > &bb)
    { polynomial<gf_p_element> tmp; gcd(tmp, aa, bb); return tmp; }

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)
    { polynomial<gf_p_element> tmp; xgcd(tmp, x, y, aa, bb); return tmp; }

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)
    { d.build_frame(polynomial< gf_p_element >::common_field(aa.ffield, bb.ffield));
      x.ffield.assign(polynomial< gf_p_element >::common_field(aa.ffield, bb.ffield));
      d.pol.assign(xgcd(x.pol, y.pol, aa.pol, bb.pol));
      d.delete_frame(); }
      
