//
// LiDIA - a library for computational number theory
//   Copyright (c) 1994, 1995, 1996 by the LiDIA Group
//
// File        : bigint_polynomial.cc
// Author      : Stefan Neis (SN)

/*
$Id: bigint_polynomial.c,v 1.7 1997/01/21 22:27:07 neis Exp $
*/

#include <LiDIA/polynomial.h>
#ifndef LIDIA_INCLUDE_C
#include <LiDIA/polynomial.c>
#endif
#include <LiDIA/random.h>

#define DV_IP LDBL_UNIPOL+8
#define DM_IP "polynomial<bigint>"
#define LP_ERROR poly_error_msg

/**
 ** Casts
 **/

  polynomial<bigint>::operator base_polynomial<bigrational>() const
  {
    base_polynomial <bigrational> x;
    x.set_degree(deg);
    for (lidia_size_t i = 0; i <= deg; i++)
      x[i] = bigrational(coeff[i]);
    return x;
  }

  polynomial<bigint>::operator base_polynomial<bigfloat>() const
  {
    base_polynomial <bigfloat> x;
    x.set_degree(deg);
    for (lidia_size_t i = 0; i <= deg; i++)
      x[i] = bigfloat(coeff[i]);
    return x;
  }

  polynomial<bigint>::operator base_polynomial<bigcomplex>() const
  {
    base_polynomial <bigcomplex> x;
    x.set_degree(deg);
    for (lidia_size_t i = 0; i <= deg; i++)
      x[i] = bigcomplex(bigfloat(coeff[i]));
    return x;
  }

/**
 ** assignment
 **/

// "polynomial <bigint> & polynomial <bigint>::operator = (const base_polynomial <bigint> &a)"
// "{"
// "  debug_handler_l("polynomial<bigint>","
// "              "operator =(const base_polynomial <bigint> &)", -1);"
// "  if (deg != a.degree()){"
// "    delete[] coeff;"
// "    deg = a.degree();"
// "    coeff = new bigint[deg + 1];"
// "  }"
// "  for (register lidia_size_t i = 0; i <= deg; i++)"
// "    coeff[i] = a[i];"
// "  return *this;"
// "}"


#ifdef __GNUG__
template class base_polynomial <bigint>;

template void negate(base_polynomial <bigint> & c,
                     const base_polynomial <bigint> &a);

template void add(base_polynomial <bigint> & c,
                  const base_polynomial <bigint> & a,
                  const base_polynomial <bigint> & b);
template void add(base_polynomial <bigint> & c,
                  const base_polynomial <bigint> & a, const bigint & b);
template void add(base_polynomial <bigint> & c,
                  const bigint & b, const base_polynomial <bigint> & a);

template void subtract(base_polynomial <bigint> & c,
                       const base_polynomial <bigint> & a,
                       const base_polynomial <bigint> & b);
template void subtract(base_polynomial <bigint> & c,
                       const base_polynomial <bigint> & a, const bigint & b);
template void subtract(base_polynomial <bigint> & c,
                       const bigint & b, const base_polynomial <bigint> & a);

template void multiply(base_polynomial <bigint> & c,
                       const base_polynomial <bigint> & a,
                       const base_polynomial <bigint> & b);
template void multiply(base_polynomial <bigint> & c,
                       const base_polynomial <bigint> & a, const bigint & b);
template void multiply(base_polynomial <bigint> & c,
                       const bigint & b, const base_polynomial <bigint> & a);

template void power(base_polynomial <bigint> & c,
                    const base_polynomial <bigint> & a, const bigint & b);

template void derivative(base_polynomial <bigint> &c,
                         const base_polynomial <bigint> &a);

template istream & operator >> (istream &, base_polynomial <bigint> &);
template ostream & operator << (ostream &, const base_polynomial <bigint> &);
#endif

/**
 ** Division
 **/

bigint cont(const base_polynomial <bigint> &a)
  {
    lidia_size_t d = a.degree();
    if (d<0) return bigint(0);
    bigint g = a[d];
    for (lidia_size_t i = d - 1; i >= 0; g = gcd(a[i--],g));
    return g;
  }

polynomial <bigint> pp(const base_polynomial <bigint> &a)
  {
    if (a.is_zero())
      return a;
    else
      return (a / cont(a));
  }


void div_rem(polynomial <bigint> &q, polynomial <bigint> &r,
              const base_polynomial <bigint> &aa, const base_polynomial <bigint> &bb)
{
  debug_handler_l(DM_IP, "in friend - function "
                  "div_rem (polynomial <bigint> &, "
                  "polynomial <bigint> &, "
                  "const base_polynomial <bigint> &, "
                  "const base_polynomial <bigint> &)", DV_IP);

  lidia_size_t deg_a = aa.degree(), deg_b = bb.degree();
  lidia_size_t deg_ab = deg_a - deg_b;
  if (deg_b < 0)
    lidia_error_handler("polynomial <bigint>", "div_rem::division by zero");

  bigint pow(1);
  if (deg_ab < 0){
    r.assign(aa);
    q.assign_zero();
  }
  else{
    const bigint *bp;
    bigint *qp, *rp, *tmp, x, y, z;
    lidia_size_t i, j, e = deg_ab + 1;

    polynomial <bigint> a=aa, b=bb;
    q.set_degree(deg_ab);
    r.assign(a);

    x = b.coeff[deg_b];

    for (i = 0, qp = q.coeff + deg_ab; i <= deg_ab; i++, qp--, e--){
      rp = r.coeff + deg_a - i;
      y = (*rp);
      for (j = deg_ab+1, tmp = q.coeff; j; j--, tmp++)
        multiply(*tmp, x, *tmp);
      for (j = deg_a+1, tmp = r.coeff; j; j--, tmp++)
        multiply(*tmp, x, *tmp);
      *qp =y;
      for (j = deg_b + 1, bp = b.coeff + deg_b; j; j--, rp--, bp--){
        multiply(z, y, *bp);
        subtract(*rp, *rp, z);
      }
    }
    q.remove_leading_zeros();
    power(pow, x, e);
    multiply(q, q, pow);
  }
  r.remove_leading_zeros();
  multiply(r, r, pow);
}

void divide(polynomial <bigint> & c,
            const base_polynomial <bigint> & a, const bigint & b)
{
  debug_handler_l(DM_IP, "in friend - function "
                  "divide (polynomial <bigint> &, "
                  "const base_polynomial <bigint> &, "
                  "const bigint &)", DV_IP);
  const bigint *ap = ((polynomial<bigint>*)(&a))->coeff;
  lidia_size_t deg_a = a.degree();

  c.set_degree(deg_a);
  bigint r, *cp = c.coeff;

  for (register lidia_size_t i = deg_a + 1; i; i--, ap++, cp++){
    div_rem ((*cp), r, (*ap), b);
    if (!(r.is_zero()))
      lidia_error_handler("polynomial <bigint>",
                          "divide(polynomial <bigint> &, "
                          "const base_polynomial <bigint> &, const bigint &)"
                          " :: division error");
  }
}

void power_mod(polynomial <bigint> & c,
               const base_polynomial <bigint> & a, const bigint & b,
               const base_polynomial <bigint> & f)
{
  debug_handler_l(DM_IP, "in friend - function "
                  "power_mod (polynomial <bigint> &, "
                  "const base_polynomial <bigint> &, const bigint &, "
                  "const base_polynomial <bigint> &)", DV_IP + 1);
  bigint exponent;
  polynomial <bigint> multiplier;
  if (b.is_negative())
    c.assign_zero();
  else if (b.is_zero() || a.is_one())
    c.assign_one();
  else{
    exponent.assign(b);
    multiplier.assign(a);
    c.assign_one();
    while (exponent.is_gt_zero()){
      if (!exponent.is_even()){
        multiply(c, c, multiplier);
        remainder(c, c, f);
      }
      multiply(multiplier, multiplier, multiplier);
      remainder(multiplier, multiplier, f);
      exponent.divide_by_2();
    }
  }
}

/**
 ** Gcd's
 **/

polynomial <bigint> gcd(const base_polynomial <bigint> &aa,
                        const base_polynomial <bigint> &bb)
// OOOh, sooooo slooowwww.... (Most naive version, should be improved
//                             to use either the sub-resultant-algorithm
//                             or modular techniques)
{
  debug_handler_l(DM_IP, "in friend - function "
                  "gcd (const base_polynomial <bigint> &, "
                  "const base_polynomial <bigint> &)", DV_IP + 2);
  if (bb.is_zero())
    return aa;
  polynomial <bigint> q, r, a = pp(aa), b = pp(bb);
  bigint cd = gcd(cont(aa), cont(bb));
  do{
    div_rem(q, r, a, b);
    a.assign(b);
    b.assign(pp(r));
  } while (b.deg >= 0);
  return cd * a;
}

polynomial <bigint> xgcd(polynomial <bigint> &x,
                         polynomial <bigint> &y,
                         const base_polynomial <bigint> &aa,
                         const base_polynomial <bigint> &bb)
// OOOh, sooooo slooowwww.... (Most naive version, should be improved
//                             to use sub-resultant-algorithm........)
{
  debug_handler_l(DM_IP, "in friend - function "
                  "xgcd (polynomial <bigint> &, polynomial <bigint> &, "
                  "const base_polynomial <bigint> &, "
                  "const base_polynomial <bigint> &)", DV_IP + 2);
  polynomial <bigint> u0, v0, u2, v2, q, r, a=aa,b=bb;

  if (b.deg < 0){
    x.assign_one();
    y.assign_zero();
    return a;
  }
  x.assign_one();
  y.assign_zero();
  u2.assign_zero();
  v2.assign_one();

  do{
    div_rem(q, r, a, b);
    a.assign(b);
    b.assign(pp(r));
    u0.assign(u2);
    v0.assign(v2);
    multiply(r, q, u2);
    subtract(u2, x, r);
    u2.assign(pp(u2));
    multiply(r, q, v2);
    subtract(v2, y, r);
    v2.assign(pp(v2));
    x.assign(u0);
    y.assign(v0);
  } while (b.deg >= 0);
  return a;
}

void remainder(polynomial <bigint> & c,
               const base_polynomial <bigint> & a, const bigint & b)
{
  debug_handler_l(DM_IP, "in friend - function "
                  "remainder (polynomial <bigint> &, "
                  "const base_polynomial <bigint> &, "
                  "const bigint &)", DV_IP + 3);
  const bigint *ap;
  bigint *cp;
  bigint r;
  register lidia_size_t deg_a = a.degree();
  register lidia_size_t i;

  c.set_degree(deg_a);

  for (i = deg_a + 1, ap = ((polynomial<bigint>*)(&a))->coeff, cp = c.coeff;
       i; i--, ap++, cp++){
    remainder (*cp, *ap, b);
    if (cp->is_negative()) add(*cp,*cp,b);
  }
}

void div_rem(polynomial <bigint> &q, polynomial <bigint> &r,
             const base_polynomial <bigint> &aa,
             const base_polynomial <bigint> &bb,
             const bigint & p)
{
  debug_handler_l(DM_IP, "in friend - function "
                  "div_rem (polynomial <bigint> &, "
                  "polynomial <bigint> &, "
                  "const base_polynomial <bigint> &, "
                  "const base_polynomial <bigint> &, "
                  "const bigint &)", DV_IP + 3);

  lidia_size_t deg_a = aa.degree(), deg_b = bb.degree();
  lidia_size_t deg_ab = deg_a - deg_b;
  if (deg_b < 0)
    lidia_error_handler("polynomial <bigint>", "div_rem mod p::division by zero");
  if (deg_ab < 0){
    remainder(r, aa, p);
    q.assign_zero();
  }
  else{
    const bigint *ap, *bp;
    bigint *qp, *rp, x, y, z;
    lidia_size_t i, j;

    polynomial <bigint> a, b;
    remainder(a, aa, p);
    remainder(b, bb, p);
    q.set_degree(deg_ab);
    r.set_degree(deg_a);

    ::xgcd(x, y, b.coeff[deg_b], p);
    if (x.is_lt_zero())
      ::add(x, x, p);

    for (i = deg_a + 1, rp = r.coeff, ap = a.coeff; i; i--, ap++, rp++)
      *rp = *ap;

    for (i = 0, qp = q.coeff+deg_ab; i <= deg_ab; i++, qp--){
      rp = r.coeff + deg_a - i;
      multiply(y, x, *rp);
      remainder(y, y, p);
      if (y.is_lt_zero())
        ::add(y, y, p);
      *qp = y;
      for (j = deg_b + 1, bp = b.coeff + deg_b; j; j--, rp--, bp--){
        multiply(z, y, *bp);
        subtract(*rp, *rp, z);
        remainder(*rp, *rp, p);
        if (rp->is_lt_zero())
          ::add(*rp, *rp, p);
      }
    }
    q.remove_leading_zeros();
  }
  r.remove_leading_zeros();
}

void power(polynomial <bigint> & c, const base_polynomial <bigint> & a,
           const bigint & b, const bigint & p)
{
  debug_handler_l(DM_IP, "in friend - function "
                  "power (polynomial <bigint> &, "
                  "const base_polynomial <bigint> &, const bigint &, "
                  "const bigint &)", DV_IP + 4);
  bigint exponent;
  polynomial <bigint> multiplier;
  if (b.is_negative())
    c.assign_zero();
  else if (b.is_zero() || a.is_one())
    c.assign_one();
  else{
    exponent.assign(b);
    multiplier.assign(a);
    c.assign_one();
    while (exponent.is_gt_zero()){
      if (!exponent.is_even()){
        multiply(c, c, multiplier);
        remainder(c, c, p);
      }
      multiply(multiplier, multiplier, multiplier);
      remainder(multiplier,multiplier, p);
      exponent.divide_by_2();
    }
  }
}

void power_mod(polynomial <bigint> & c,
               const base_polynomial <bigint> & a, const bigint & b,
               const base_polynomial <bigint> & f, const bigint & p)
{
  debug_handler_l(DM_IP, "in friend - function "
                  "power_mod (polynomial <bigint> &, "
                  "const base_polynomial <bigint> &, const bigint &, "
                  "const base_polynomial <bigint> &, const bigint &, ",
                  DV_IP + 4);
  bigint exponent;
  polynomial <bigint> multiplier, garbage;
  if (b.is_negative())
    c.assign_zero();
  else if (b.is_zero() || a.is_one())
    c.assign_one();
  else{
    exponent.assign(b);
    multiplier.assign(a);
    c.assign_one();
    while (exponent.is_gt_zero()){
      if (!exponent.is_even()){
        multiply(c, c, multiplier);
        //      c %= p;
        div_rem(garbage, c, c, f, p);
      }
      multiply(multiplier, multiplier, multiplier);
      //      remainder(multiplier, multiplier, p);
      div_rem(garbage, multiplier, multiplier, f, p);
      exponent.divide_by_2();
    }
  }
}

polynomial <bigint> gcd(const base_polynomial <bigint> &aa,
                        const base_polynomial <bigint> &bb, const bigint & p)
{
  debug_handler_l(DM_IP, "in friend - function "
                  "gcd (const base_polynomial <bigint> &, "
                  "const base_polynomial <bigint> &, const bigint &)",
                  DV_IP + 5);
  polynomial <bigint> a,b;
  remainder(a, aa, p);
  remainder(b, bb, p);
  if (b.deg < 0)
    return a;
  polynomial <bigint> q, r;
  do{
    div_rem(q, r, a, b, p); // div_rem mod p!
    a.assign(b);
    b.assign(r);
  } while (b.deg >= 0);

  bigint x,y;
  ::xgcd(x, y, a.coeff[a.deg], p);
  if (x.is_lt_zero())
    ::add(x, x, p);

  bigint *ap =a.coeff;
  for (register lidia_size_t i = a.deg + 1; i; i--, ap++){
    multiply(*ap, *ap, x);
    remainder(*ap, *ap, p);
    if (ap->is_lt_zero())
      ::add(*ap, *ap, p);
  }
  return a;
}

void squarefree_factor(const base_polynomial <bigint> &f,
                       polynomial < bigint> * fa, const bigint & p)
{
  debug_handler_c(DM_IP, "in friend - function "
                  "squarefree_factor (const base_polynomial <bigint> &, "
                  "polynomial < bigint> *, const bigint &)", DV_IP + 7,
                  cout << "Factoring "<<f <<" mod "<<p<<endl);

  register lidia_size_t i, j;
  polynomial <bigint> d, t, r, v, w;
  lidia_size_t e, k;
  lidia_size_t p_size_t;
  int step2;

  e=1;

  polynomial <bigint> t0 = f;
  for (i=1; i<=f.degree(); i++){
    fa[i].assign_one();
  }

  step2=1;
  do{
    if (step2){
      debug_handler_c(DM_IP, "squarefree_factor", DV_IP + 6,
                      cout << "STEP 2 : t0="<<t0<<flush);
      if (t0.degree()<=0) break;
      derivative(d, t0);
      remainder(d, d, p);         // derivative
      debug_handler_c(DM_IP,"squarefree_factor",DV_IP + 6,
                      cout << "STEP 2 : derivative (p)="<<d<<flush);
      t = gcd(t0,d,p);    // gcd
      debug_handler_c(DM_IP,"squarefree_factor",DV_IP + 6,
                      cout << "STEP 2 : gcd ="<<t<<flush);

      div_rem(v,r,t0,t,p);
      debug_handler_c(DM_IP,"squarefree_factor",DV_IP + 6,
                      cout << "STEP 2 : quotient ="<<v<<flush);
      k=0;
    }

    if (v.degree()<=0){                // derivative=0
      if (t.degree()<=0) break;
      if (p.sizetify(p_size_t)){
        lidia_error_handler_c(DM_IP,"squarefree_factor(....)"
                              "::error in sizetify",
                              cout << p << " is not an lidia_size_t.";
                              cout << endl);
      }

      t0.set_degree(t.degree()/p_size_t);
      for (j=0; j<=t.degree(); j+=p_size_t)
        t0.coeff[j/p_size_t]=t.coeff[j];

      debug_handler_c(DM_IP,"squarefree_factor",DV_IP + 6,
                      cout << "STEP 3 : t ="<<t<<flush);
      e=p_size_t*e;
      step2=1;
      continue;
    }

    k++;
    debug_handler_c(DM_IP,"squarefree_factor",DV_IP + 6,
                    cout << "STEP 4 : k =" << k << endl<<flush);
    if (bigint(k) % p==0){
      div_rem(t,r,t,v,p);
      k++;
    }
    w = gcd(t,v,p);
    debug_handler_c(DM_IP,"squarefree_factor",DV_IP + 6,
                    cout << "STEP 5 : w ="<<w<< "Now dividing ";
                    cout<<v << " by "<<w<<endl<<flush);
    div_rem(fa[e*k],r,v,w,p);
    debug_handler_c(DM_IP,"squarefree_factor",DV_IP + 6,
                    cout << "Result is ");
    debug_handler_c(DM_IP,"squarefree_factor",DV_IP + 6,
                    cout << "STEP 5 : fa["<<e*k<<"]="<<fa[e*k];
                    cout << " with remainder "<< r << endl << flush);
    remainder(v, w, p);
    debug_handler_c(DM_IP,"squarefree_factor",DV_IP + 6,
                    cout << "Now dividing "<<t<<" by "<<v<<endl<<flush);
    div_rem(t,r,t,v,p);
    debug_handler_c(DM_IP,"squarefree_factor",DV_IP + 6,
                    cout << "Result is ");
    debug_handler_c(DM_IP,"squarefree_factor",DV_IP + 6,
                    cout << "STEP 5 : t ="<<t;
                    cout << " with remainder "<< r << endl << flush);
    step2=0;
  }
  while (1);
}

int ddf(const base_polynomial <bigint> &f0,
        polynomial < bigint> * fa, const bigint & p)
    /* return 1 means the polynomial f is not squarefree mod p */
    /*          or    the polynomial f has 0 as its root       */
    /* return 0 means h[i] contains the number of irreducible factors
       of degree i */
    /* "Computational algebraic number theory"(M. Pohst)Birkhaeuser Verlag */
{
  debug_handler_l(DM_IP, "in friend - function "
                  "ddf (const base_polynomial <bigint> &, "
                  "polynomial < bigint> *, const bigint &)", DV_IP + 7);

  int i,j;
  polynomial <bigint> f1, f, d, h, q, r;
  bigint p_pow;

  remainder(f, f0, p);
  if (f.coeff[0]==0){
    return 1; /* divisible by X */
  }
  derivative(f1, f);
  remainder(f1, f1, p);     /* coefficients of f' in af1 */

  d = gcd(f, f1, p);
                                          /* gcd(f,f') */
  if (d.deg>0){
    return 1;   /* f not even squarefree */
  }

  // Now, we know that f is squarefree and not divisible by X

  h.assign_x();

  for (i = 1; i <= f0.degree(); i++){
    if (f.deg>>1 < i){
      for (j=i+1;j <= f0.degree(); j++) fa[j].assign_one();
      fa[f.deg].assign(f);
      break;
    }
    debug_handler_c(DM_IP,"ddf",-1,
                    cout << "compute "<<p<<"th power of "<<h<<endl<<flush);
    power_mod(h,h,p,f,p); /* h(X)=X^(p^i) mod (p,f) */
    debug_handler_c(DM_IP,"ddf",-1,
                    cout << "h = X^(p^"<<i<<") mod "<<f<<" is ";
                    cout << h << endl << flush);
    h.coeff[1]--;
    if (h.coeff[1]<0) h.coeff[1]+=p;
    debug_handler_c(DM_IP,"ddf",-1,
                    cout << "X^(p^"<<i<<")-X mod "<<f;
                    cout <<" is "<<h<<endl<<flush);
    d = gcd(f, h, p);
    debug_handler_c(DM_IP,"ddf",-1,
                    cout << "gcd with f is "<<d<<endl<<flush);
    div_rem(f, r, f, d, p);
    debug_handler_c(DM_IP,"ddf",-1,
                    cout << "f divided by gcd: "<<f<<r<<endl<<flush);

    /* f has (deg d) / i divisors of degree i */
    fa[i] = d;
    h.coeff[1]++;
    if (h.coeff[1]>=p) h.coeff[1]-=p;
  }
  return 0; /* f is squarefree mod p */
}

void can_zass(const base_polynomial <bigint> &f,
              polynomial < bigint> * fa, lidia_size_t d, const bigint & p,
              lidia_size_t count)
{
  debug_handler_l(DM_IP, "in friend - function "
                  "can_zass (const base_polynomial <bigint> &, "
                  "polynomial < bigint> *, lidia_size_t, "
                  "const bigint &, lidia_size_t)", DV_IP + 7);
  polynomial <bigint> b, garbage;
  if (p != 2){
    bigint p_pow;
    seed(p);
    do {
      // Compute monic random t with deg(t)<= 2d-1.
      long temp;
      temp = random() % (2 * d - 1);    // so 0 <= temp <= 2d - 2
      temp++;                           // so 1 <= temp <= 2d - 1
      polynomial <bigint> t;
      t.set_degree(temp);
      t.coeff[t.deg].assign_one();
      for (register lidia_size_t i = 0; i < t.deg; i++)
        t.coeff[i].assign(randomize(p));
      debug_handler_c(DM_IP,"can_zass",-1,
                      cout << "Trying to split " << f;
                      cout << " with " << t << endl << flush);
      // Compute t^((p^d-1)/2) - 1;
      power(p_pow,p,d);
      p_pow>>=1;
      power_mod(b,t,p_pow,f,p);
      b.coeff[0]--;
      if (b.coeff[0]<0) b.coeff[0]+=p;
      debug_handler_c(DM_IP,"can_zass",-1,
                      cout << "t^((p^d-1)/2) - 1 is t^" << p_pow;
                      cout << " = " << b << endl << flush);
      // Compute gcd with f;
      b=gcd(b,f,p);
      debug_handler_c(DM_IP,"can_zass",-1,
                      cout << "gcd is "<<b<<endl<<flush);
    } while (b.deg == 0 || b.deg == f.degree());
  }
  else{
    polynomial <bigint> x;
    x.assign_x();
    polynomial <bigint> t(x), c, n;
    while (1) {
      c.assign(t);
      n.assign(t);
      for (register lidia_size_t i = d - 1; i; i--){
        multiply(n, n, n);
        remainder(n, n, p);
        div_rem(garbage, n, n, f, p);
        add(c, c, n);
        remainder(c, c, p);
      }
      b = gcd(f, c, p);
      if (b.deg == 0 || b.deg == f.degree()){
        multiply(t, t, x);
        multiply(t, t, x);
      }
      else break;
    }
  }
  // Apply the algorithm recursively
  if (b.deg == d)
    fa[count++] = b;
  else{
    can_zass(b, fa, d, p, count);
    count += b.deg / d;
  }
  div_rem(b,garbage,f,b,p);
  if (b.deg == d)
    fa[count++] = b;
  else{
    can_zass(b, fa, d, p, count);
    count += b.deg / d;
  }
}

void factor(const base_polynomial <bigint> &f,
            polynomial < bigint> * fa, lidia_size_t * exponent, const bigint & p)
{
  debug_handler_l(DM_IP, "in friend - function "
                  "factor (const base_polynomial <bigint> &, "
                  "polynomial < bigint> *, lidia_size_t *, "
                  "const bigint &)", DV_IP + 7);
  polynomial <bigint> * sqf_factor = new polynomial <bigint> [f.degree()+1];
  polynomial <bigint> * ddf_factor = new polynomial <bigint> [f.degree()+1];
  lidia_size_t count = 0;
  register lidia_size_t i = 0;

  for (; i < f.degree(); i++){
    fa[i].assign_one();
    exponent[i] = 0;
  }

  squarefree_factor(f, sqf_factor, p);
  debug_handler_l(DM_IP, "factor(...)::"
                  "returned from squarefree_factor",-1);

  for (i = 1; i <= f.degree(); i++)
    if (sqf_factor[i].deg > 0 ){
      if (sqf_factor[i].deg == 1){
        fa[count] = sqf_factor[i];
        exponent[count++] = i;
      }
      else{
        ddf(sqf_factor[i], ddf_factor, p);
        debug_handler_l(DM_IP, "factor(...)::"
                        "returned from ddf",-1);
        // Now call can_zass on each ddf_factor, where necessary.
        for (register lidia_size_t j = 1; j <= sqf_factor[i].deg; j++)
          if (ddf_factor[j].deg == j){
            fa[count].assign(ddf_factor[j]);
            exponent[count++] = i;
          }
          else if (ddf_factor[j].deg > 0){
            can_zass(ddf_factor[j],fa,j,p,count);
            debug_handler_l(DM_IP, "factor(...)::"
                            "returned from can_zass",-1);
            for (register lidia_size_t k = 0;
                 k < ddf_factor[j].deg /j; k++)
              exponent[count++]=i;
            debug_handler_l(DM_IP, "factor(...)::"
                            "stored factors",-1);
          }
      }
    }
  delete[] ddf_factor;
  delete[] sqf_factor;
  debug_handler_l(DM_IP, "factor(...)::"
                  "freed temporarily allocated space",-1);

}

lidia_size_t no_of_real_roots(const base_polynomial<bigint>& poly_T)
{
  debug_handler_l(DM_IP, "in friend - function "
                  "no_of_real_roots (const base_polynomial <bigint> &)",
                  DV_IP + 8);

  if (poly_T.degree() <= 0)
    return 0;

  if (poly_T.degree() == 1)
    return 1;

  polynomial<bigint> A=pp(poly_T);
  polynomial<bigint> B=pp(derivative(poly_T));

  lidia_size_t n = A.deg, s = lead_coeff(A).sign(), t,
    r1 = 1, del, te, ll, degr = 1;

  bigint g = 1, h = 1;

  if (n%2)
    t = s;
  else
    t = -s;

  polynomial<bigint> R,Q;
  while (degr){
    del = A.deg - B.deg;
    div_rem(Q,R,A,B);
    if (R.deg < 0)
      error_handler("sturm","no_of_real_roots::argument was not square free");
    if (lead_coeff(B)>0 || del%2==1){
      R = -R;
    }
    degr = R.deg;
    te = lead_coeff(R).sign();
    if (te!=s){
      s=-s;
      r1=r1-1;
    }
    if (degr%2)
      ll = -t;
    else
      ll = t;
    if (te!=ll){
      t = -t;
      r1++;
    }
    if (degr!=0){
      A=B;
      bigint bi,bi2;
      power(bi,h,del);
      multiply(bi, g, bi);
      divide(B, R, bi);
      g=abs(lead_coeff(A));
      power(bi2, g, del);
      power(bi, h, del-1);
      divide(h, bi2, bi);
    }
  }
  return r1;
}
#undef DV_IP
#undef DM_IP
#undef LP_ERROR
