/*
 * pcfrac: Implementation of the continued fraction factoring algoritm
 *
 * Every two digits additional appears to double the factoring time
 *
 * Written by Dave Barrett (barrett%asgard@boulder.Colorado.EDU)
 */
#include <string.h>
#include <stdio.h>
#include <math.h>

#ifdef __STDC__
#include <stdlib.h>
#endif
#include "precision.h"
#include "pfactor.h"

extern void aos_free(void * mem);
extern void * aos_malloc(int size);


extern int verbose;

unsigned cfracNabort  = 0;
unsigned cfracTsolns  = 0;
unsigned cfracPsolns  = 0;
unsigned cfracT2solns = 0;
unsigned cfracFsolns  = 0;

extern unsigned short primes[];
extern unsigned primesize;

typedef unsigned *uptr;
typedef uptr	 uvec;
typedef unsigned char *solnvec;
typedef unsigned char *BitVector;

typedef struct SolnStruc {
   struct SolnStruc *next;
   precision x;		/* lhs of solution */
   precision t;		/* last large prime remaining after factoring */
   precision r;		/* accumulated root of pm for powers >= 2 */
   BitVector e;		/* bit vector of factorbase powers mod 2 */
} Soln;

typedef Soln *SolnPtr;

#define BPI(x)	((sizeof x[0]) << 3)

void setBit(bv, bno, value)
   register BitVector bv;
   register unsigned bno, value;
{
   bv  += bno / BPI(bv);
   bno %= BPI(bv);
   *bv |= ((value != 0) << bno);
}

unsigned getBit(bv, bno)
   register BitVector bv;
   register unsigned bno;
{
   register unsigned res;

   bv  += bno / BPI(bv);
   bno %= BPI(bv);
   res  = (*bv >> bno) & 1;

   return res;
}

BitVector newBitVector(value, size)
   register solnvec value;
   unsigned size;
{
   register BitVector res;
   register solnvec  vp = value + size;
   unsigned msize = ((size + BPI(res)-1) / BPI(res)) * sizeof res[0];

#ifdef BWGC
   res = (BitVector) gc_malloc(msize);
#else
   res = (BitVector) aos_malloc(msize);
#endif
   if (res == (BitVector) 0) return res;

   memset(res, '\0', msize);
   do {
      if (*--vp) {
	 setBit(res, vp - value, (unsigned) *vp);
      }
   } while (vp != value);
   return res;
}

void printSoln(stream, prefix, suffix, pm, m, p, t, e)
   FILE *stream;
   char *prefix, *suffix;
   register unsigned *pm, m;
   precision p, t;
   register solnvec e;
{
   register unsigned i, j = 0;

   for (i = 1; i <= m; i++) j += (e[i] != 0);

   fputs(prefix, stream);
   fputp(stream, pparm(p)); fputs(" = ", stream);
   if (*e & 1) putc('-', stream);  else putc('+', stream);
   fputp(stream, pparm(t));

   if (j >= 1) fputs(" *", stream);
   do {
      e++;
      switch (*e) {
      case 0: break;
      case 1: fprintf(stream, " %u", *pm); break;
      default:
	 fprintf(stream, " %u^%u", *pm, (unsigned) *e);
      }
      pm++;
   } while (--m);

   fputs(suffix, stream);
   fflush(stream);
   pdestroy(p); pdestroy(t);
}

/*
 * Combine two solutions
 */
void combineSoln(x, t, e, pm, m, n, bp)
   precision *x, *t, n;
   uvec	     pm;
   register  solnvec e;
   unsigned  m;
   SolnPtr   bp;
{
   register unsigned j;

   (void) pparm(n);
   if (bp != (SolnPtr) 0) {
      pset(x, pmod(pmul(bp->x, *x), n));
      pset(t, pmod(pmul(bp->t, *t), n));
      pset(t, pmod(pmul(bp->r, *t), n));
      e[0] += getBit(bp->e, 0);
   }
   e[0] &= 1;
   for (j = 1; j <= m; j++) {
      if (bp != (SolnPtr) 0) e[j] += getBit(bp->e, j);
      if (e[j] > 2) {
	 pset(t, pmod(pmul(*t,
	    ppowmod(utop(pm[j-1]), utop((unsigned) e[j]>>1), n)), n));
	 e[j] &= 1;
      } else if (e[j] == 2) {
	 pset(t, pmod(pmul(*t, utop(pm[j-1])), n));
	 e[j] = 0;
      }
   }
   pdestroy(n);
}

/*
 * Create a normalized solution structure from the given inputs
 */
SolnPtr newSoln(n, pm, m, next, x, t, e)
   precision n;
   unsigned  m;
   uvec     pm;
   SolnPtr next;
   precision x, t;
   solnvec e;
{
#ifdef BWGC
   SolnPtr bp = (SolnPtr) gc_malloc(sizeof (Soln));
#else
   SolnPtr bp = (SolnPtr) aos_malloc(sizeof (Soln));
#endif

   if (bp != (SolnPtr) 0) {
      bp->next = next;
      bp->x = pnew(x);
      bp->t = pnew(t);
      bp->r = pnew(pone);
      /*
       * normalize e, put the result in bp->r and e
       */
      combineSoln(&bp->x, &bp->r, e, pm, m, pparm(n), (SolnPtr) 0);
      bp->e = newBitVector(e, m+1);			/* BitVector */
   }

   pdestroy(n);
   return bp;
}

void freeSoln(p)
   register SolnPtr p;
{
   if (p != (SolnPtr) 0) {
      pdestroy(p->x);
      pdestroy(p->t);
      pdestroy(p->r);
#ifndef IGNOREFREE
      aos_free(p->e);			/* BitVector */
      aos_free(p);
#endif
   }
}

void freeSolns(p)
   register SolnPtr p;
{
   register SolnPtr l;

   while (p != (SolnPtr) 0) {
      l = p;
      p = p->next;
      freeSoln(l);
   }
}

SolnPtr findSoln(sp, t)
   register SolnPtr sp;
   precision t;
{
   (void) pparm(t);
   while (sp != (SolnPtr) 0) {
      if peq(sp->t, t) break;
      sp = sp->next;
   }
   pdestroy(t);
   return sp;
}

static unsigned pcfrac_k      = 1;
static unsigned pcfrac_m      = 0;
static unsigned pcfrac_aborts = 3;

/*
 * Structure for early-abort.  Last entry must be <(unsigned *) 0, uUndef>
 */
typedef struct {
   unsigned  *pm;	/* bound check occurs before using this pm entry */
   precision bound;	/* max allowable residual to prevent abort */
} EasEntry;

typedef EasEntry *EasPtr;

void freeEas(eas)
   EasPtr eas;
{
   register EasPtr ep = eas;

   if (ep != (EasPtr) 0) {
      while (ep->pm != 0) {
	 pdestroy(ep->bound);
	 ep++;
      }
#ifndef IGNOREFREE
      aos_free(eas);
#endif
   }
}

/*
 * Return Pomerance's L^alpha (L = exp(sqrt(log(n)*log(log(n)))))
 */
double pomeranceLpow(n, y)
   double n;
   double y;
{
   double lnN = log(n);
   double res = exp(y * sqrt(lnN * log(lnN)));
   return res;
}

/*
 * Pomerance's value 'a' from page 122 "of Computational methods in Number
 * Theory", part 1, 1982.
 */
double cfracA(n, aborts)
   double n;
   unsigned aborts;
{
   return 1.0 / sqrt(6.0 + 2.0 / ((double) aborts + 1.0));
}

/*
 * Returns 1 if a is a quadratic residue of odd prime p,
 * p-1 if non-quadratic residue, 0 otherwise (gcd(a,p)<>1)
 */
#define plegendre(a,p) ppowmod(a, phalf(psub(p, pone)), p)

/*
 * Create a table of small primes of quadratic residues of n
 *
 * Input:
 *    n      - the number to be factored
 *    k      - the multiple of n to be factored
 *    *m     - the number of primes to generate (0 to select best)
 *    aborts - the number of early aborts
 *
 * Assumes that plegendre # 0, for if it is, that pm is a factor of n.
 * This algorithm already assumes you've used trial division to eliminate
 * all of these!
 *
 * Returns: the list of primes actually generated (or (unsigned *) 0 if nomem)
 *          *m changed to reflect the number of elements in the list
 */
uvec pfactorbase(n, k, m, aborts)
   precision n;
   unsigned k;
   unsigned *m, aborts;
{
   double   dn, a;
   register unsigned short *primePtr = primes;
   register unsigned count = *m;
   unsigned maxpm = primes[primesize-1];
   unsigned *res  = (uvec) 0, *pm;
   precision nk   = pnew(pmul(pparm(n), utop(k)));

   if (*m == 0) {			/* compute a suitable m */
      dn    = ptod(nk);
      a     = cfracA(dn, aborts);
      maxpm = (unsigned) (pomeranceLpow(dn, a) + 0.5);
      do {
	 if ((unsigned) *primePtr++ >= maxpm) break;
      } while ((unsigned) *primePtr != 1);
      count = primePtr - primes;
      primePtr = primes;
   }
   /*
    * This m tends to be too small for small n, and becomes closer to
    * optimal as n goes to infinity.  For 30 digits, best m is ~1.5 this m.
    * For 38 digits, best m appears to be ~1.15 this m. It's appears to be
    * better to guess too big than too small.
    */
#ifdef BWGC
   res = (uvec) gc_malloc(count * sizeof (unsigned));
#else
   res = (uvec) aos_malloc(count * sizeof (unsigned));
#endif
   if (res == (uvec) 0) goto doneMk;

   pm    = res;
   *pm++ = (unsigned) *primePtr++;		/* two is first element */
   count = 1;
   if (count != *m) do {
      if (picmp(plegendre(nk, utop((unsigned) *primePtr)), 1) <= 0) { /* 0,1 */
	 *pm++ = *primePtr;
	 count++;
	 if (count == *m) break;
	 if ((unsigned) *primePtr >= maxpm) break;
      }
      ++primePtr;
   } while (*primePtr != 1);
   *m = count;

doneMk:
   pdestroy(nk);
   pdestroy(n);
   return res;
}

/*
 * Compute Pomerance's early-abort-stragegy
 */
EasPtr getEas(n, k, pm, m, aborts)
   precision n;
   unsigned k, *pm, m, aborts;
{
   double x    = 1.0 / ((double) aborts + 1.0);
   double a    = 1.0 / sqrt(6.0 + 2.0 * x);
   double ax   = a * x, csum = 1.0, tia = 0.0;
   double dn, dpval, dbound, ci;
   unsigned i, j, pval;

   precision bound = pUndef;
   EasPtr   eas;

   if (aborts == 0) return (EasPtr) 0;

#ifdef BWGC
   eas = (EasPtr) gc_malloc((aborts+1) * sizeof (EasEntry));
#else
   eas = (EasPtr) aos_malloc((aborts+1) * sizeof (EasEntry));
#endif
   if (eas == (EasPtr) 0) return eas;

   dn   = ptod(pmul(utop(k), pparm(n)));	/* should this be n ? */
   for (i = 1; i <= aborts; i++) {
      eas[i-1].pm = (unsigned *) 0;
      eas[i-1].bound = pUndef;
      tia        += ax;
      ci          = 4.0 * tia * tia / (double) i;
      csum       -= ci;
      dpval 	  = pomeranceLpow(dn, tia);
      dbound      = pow(dn, 0.5 * csum);

      pval	  = (unsigned) (dpval + 0.5);
      pset(&bound, dtop(dbound));
      for (j = 0; j < m; j++) {
	 if (pm[j] >= pval) goto foundpm;
      }
      break;
foundpm:
      if (verbose > 1) {
	 printf(" Abort %u on p = %u (>=%u) and q > ", i, pm[j], pval);
	 fputp(stdout, bound);  putc('\n', stdout);
	 fflush(stdout);
      }
      eas[i-1].pm = &pm[j];
      pset(&eas[i-1].bound, bound);
   }
   eas[i-1].pm = (unsigned *) 0;
   eas[i-1].bound = pUndef;

   pdestroy(bound);
   pdestroy(n);

   return eas;
}

/*
 * Factor the argument Qn using the primes in pm.  Result stored in exponent
 * vector e, and residual factor, f.  If non-null, eas points to a list of
 * early-abort boundaries.
 *
 * e is set to the number of times each prime in pm divides v.
 *
 * Returns:
 *    -2 - if factoring aborted because of early abort
 *    -1 - factoring failed
 *     0 - if result is a "partial" factoring
 *     1 - normal return (a "full" factoring)
 */
int pfactorQ(f, t, pm, e, m, eas)
   precision *f;
   precision t;
   register unsigned *pm;
   register solnvec  e;
   register unsigned m;
   EasEntry *eas;
{
   precision maxp  = pUndef;
   unsigned  maxpm = pm[m-1], res = 0;
   register unsigned *pp = (unsigned *) 0;

   (void) pparm(t);

   if (eas != (EasEntry *) 0) {
      pp = eas->pm;
      pset(&maxp, eas->bound);
   }

   memset((char *) e, '\0', m * sizeof e[0]);  /* looks slow here, but isn't */

   while (peven(t)) {		/* assume 2 1st in pm; save time */
      pset(&t, phalf(t));
      (*e)++;
   }
   --m;

   do {
      e++; pm++;
      if (pm == pp) {		/* check for early abort */
	 if (pgt(t, maxp)) {
	    res = -2;
	    goto gotSoln;
	 }
	 eas++;
	 pp = eas->pm;
	 pset(&maxp, eas->bound);
      }
      while (pimod(t, (int) *pm) == 0) {
	 pset(&t, pidiv(t, (int) *pm));
	 (*e)++;
      }
   } while (--m != 0);
   res = -1;
   if (picmp(t, 1) == 0) {
      res = 1;
   } else if (picmp(pidiv(t, (int) *pm), maxpm) <= 0) {
#if 0 		/* it'll never happen;  Honest! If so, pm is incorrect. */
      if (picmp(t, maxpm) <= 0) {
	 fprintf(stderr, "BUG: partial with t < maxpm! t = ");
	 fputp(stderr, t); putc('\n', stderr);
      }
#endif
      res = 0;
   }
gotSoln:
   pset(f, t);
   pdestroy(t); pdestroy(maxp);
   return res;
}

/*
 * Attempt to factor n using continued fractions (n must NOT be prime)
 *
 * n        - The number to attempt to factor
 * maxCount - if non-null, points to the maximum number of iterations to try.
 *
 * This algorithm may fail if it get's into a cycle or maxCount expires
 * If failed, n is returned.
 *
 * This algorithm will loop indefinitiely in n is prime.
 *
 * This an implementation of Morrison and Brillhart's algorithm, with
 * Pomerance's early abort strategy, and Knuth's method to find best k.
 */
precision pcfrac(n, maxCount)
   precision n;
   unsigned *maxCount;
{
   unsigned k      = pcfrac_k;
   unsigned m      = pcfrac_m;
   unsigned aborts = pcfrac_aborts;
   SolnPtr  oddt   = (SolnPtr) 0, sp, bp, *b;
   EasPtr   eas    = (EasPtr) 0;
   uvec     pm     = (uvec) 0;
   solnvec  e	   = (solnvec) 0;
   unsigned bsize, s = 0, count = 0;
   register unsigned h, j;
   int i;

   precision t = pUndef,
      r  = pUndef, twog   = pUndef,  u = pUndef, lastU  = pUndef,
      Qn = pUndef, lastQn = pUndef, An = pUndef, lastAn = pUndef,
      x  = pUndef, y      = pUndef, qn = pUndef,     rn = pUndef;

   precision res = pnew(pparm(n));		/* default res is argument */

   pm = pfactorbase(n, k, &m, aborts); 		/* m may have been reduced */

   bsize  = (m+2) * sizeof (SolnPtr);
#ifdef BWGC
   b      = (SolnPtr *) gc_malloc(bsize);
#else
   b      = (SolnPtr *) aos_malloc(bsize);
#endif
   if (b == (SolnPtr *) 0) goto nomem;

#ifdef BWGC
   e = (solnvec) gc_malloc((m+1) * sizeof e[0]);
#else
   e = (solnvec) aos_malloc((m+1) * sizeof e[0]);
#endif
   if (e == (solnvec) 0) {
nomem:
      errorp(PNOMEM, "pcfrac", "out of memory");
      goto bail;
   }

   memset(b, '\0', bsize);				 /* F1: Initialize */
   if (maxCount != (unsigned *) 0) count = *maxCount;
   cfracTsolns = cfracPsolns = cfracT2solns = cfracFsolns = cfracNabort = 0;

   eas = getEas(n, k, pm, m, aborts);		/* early abort strategy */

   if (verbose > 1) {
      fprintf(stdout, "factorBase[%u]: ", m);
      for (j = 0; j < m; j++) {
	 fprintf(stdout, "%u ", pm[j]);
      }
      putc('\n', stdout);
      fflush(stdout);
   }

   pset(&t,      pmul(utop(k), n)); 		/* E1: Initialize */
   pset(&r,      psqrt(t));			/* constant: sqrt(k*n) */
   pset(&twog,   padd(r, r));			/* constant: 2*sqrt(k*n) */
   pset(&u,      twog);				/* g + Pn */
   pset(&lastU,  twog);
   pset(&Qn,     pone);
   pset(&lastQn, psub(t, pmul(r, r)));
   pset(&An,     pone);
   pset(&lastAn, r);
   pset(&qn,     pzero);

   do {
F2:
      do {
	 if (--count == 0) goto bail;
	 pset(&t, An);
	 pdivmod(padd(pmul(qn, An), lastAn), n, pNull, &An);  	/* (5) */
	 pset(&lastAn, t);

	 pset(&t, Qn);
	 pset(&Qn, padd(pmul(qn, psub(lastU, u)), lastQn));	/* (7) */
	 pset(&lastQn, t);

	 pset(&lastU, u);

	 pset(&qn, pone);		/* eliminate 40% of next divmod */
	 pset(&rn, psub(u, Qn));
	 if (pge(rn, Qn)) {
	    pdivmod(u, Qn, &qn, &rn);				/* (4) */
	 }
	 pset(&u, psub(twog, rn));				/* (6) */
	 s = 1-s;

	 e[0] = s;
	 i = pfactorQ(&t, Qn, pm, &e[1], m, eas);  	/* E3: Factor Qn */
	 if (i < -1) cfracNabort++;
	 /*
	  * We should (but don't, yet) check to see if we can get a
	  * factor by a special property of Qn = 1
	  */
	 if (picmp(Qn, 1) == 0) {
	    errorp(PDOMAIN, "pcfrac", "cycle encountered; pick bigger k");
	    goto bail;			/* we ran into a cycle; give up */
	 }
      } while (i < 0); 			/* while not a solution */

      pset(&x, An); 	/* End of Algorithm E; we now have solution: <x,t,e> */

      if (i == 0) {	/* if partial */
	 if ((sp = findSoln(oddt, t)) == (SolnPtr) 0) {
	    cfracTsolns++;
	    if (verbose >= 2) putc('.', stderr);
	    if (verbose > 3) printSoln(stdout, "Partial: ","\n", pm,m,x,t,e);
	    oddt = newSoln(n, pm, m, oddt, x, t, e);
	    goto F2;			/* wait for same t to occur again */
	 }
	 if (verbose > 3) printSoln(stdout, "Partial: ", " -->\n", pm,m,x,t,e);
	 pset(&t, pone);		/* take square root */
         combineSoln(&x, &t, e, pm, m, n, sp);
	 cfracT2solns++;
	 if (verbose) putc('#', stderr);
	 if (verbose > 2) printSoln(stdout, "PartSum: ", "", pm, m, x, t, e);
      } else {
	 combineSoln(&x, &t, e, pm, m, n, (SolnPtr) 0);		/* normalize */
	 cfracPsolns++;
	 if (verbose) putc('*', stderr);
	 if (verbose > 2) printSoln(stdout, "Full:    ", "", pm, m, x, t, e);
      }

      /*
       * Crude gaussian elimination.  We should be more effecient about the
       * binary vectors here, but this works as it is.
       *
       * At this point, t must be pone, or t occurred twice
       *
       * Loop Invariants:  e[0:h] even
       *                   t^2 is a product of squares of primes
       *                   b[h]->e[0:h-1] even and b[h]->e[h] odd
       */
      h = m+1;
      do {
	 --h;
	 if (e[h]) {				/* F3: Search for odd */
	    bp=b[h];
	    if (bp == (SolnPtr) 0) { 		/* F4: Linear dependence? */
	       if (verbose > 3)  {
		  printSoln(stdout, " -->\nFullSum: ", "", pm, m, x, t, e);
	       }
	       if (verbose > 2) putc('\n', stdout);
	       b[h] = newSoln(n, pm, m, bp, x, t, e);
	       goto F2;
	    }
	    combineSoln(&x, &t, e, pm, m, n, bp);
	 }
      } while (h != 0);
      /*
       * F5: Try to Factor: We have a perfect square (has about 50% chance)
       */
      cfracFsolns++;
      pset(&y, t);				/* t is already sqrt'd */

      switch (verbose) {
      case 0: break;
      case 1: putc('/', stderr); break;
      case 2: putc('\n', stderr); break;
      default: ;
	 putc('\n', stderr);
	 printSoln(stdout, " -->\nSquare:  ", "\n", pm, m, x, t, e);
	 fputs("x,y:     ", stdout);
	 fputp(stdout, x); fputs("  ", stdout);
	 fputp(stdout, y); putc('\n', stdout);
	 fflush(stdout);
      }
   } while (peq(x, y) || peq(padd(x, y), n));	/* while x = +/- y */

   pset(&res, pgcd(padd(x, y), n));		/* factor found at last */

   /*
    * Check for degenerate solution.  This shouldn't happen. Detects bugs.
    */
   if (peq(res, pone) || peq(res, n)) {
      fputs("Error!  Degenerate solution:\n", stdout);
      fputs("x,y:   ", stdout);
      fputp(stdout, x); fputs(" ", stdout);
      fputp(stdout, y); putc('\n', stdout);
      fflush(stdout);
      abort();
   }

bail:
   if (maxCount != (unsigned *) 0) *maxCount = count;

   if (b != (SolnPtr *) 0) for (j = 0; j <= m; j++) freeSoln(b[j]);
   freeEas(eas);
   freeSolns(oddt);
#ifndef IGNOREFREE
   aos_free(e);
   aos_free(pm);
#endif

   pdestroy(r);  pdestroy(twog);   pdestroy(u);  pdestroy(lastU);
   pdestroy(Qn); pdestroy(lastQn); pdestroy(An); pdestroy(lastAn);
   pdestroy(x);  pdestroy(y);      pdestroy(qn); pdestroy(rn);
   pdestroy(t);  pdestroy(n);
#ifndef IGNOREFREE
   aos_free(b);
#endif
   return presult(res);
}

/*
 * Initialization for pcfrac factoring method
 *
 * k      - An integer multiplier to use for n (k must be < n)
 *            you can use findk to get a good value.  k should be squarefree
 * m      - The number of primes to use in the factor base
 * aborts - the number of early aborts to use
 */
int pcfracInit(m, k, aborts)
   unsigned m;
   unsigned k;
   unsigned aborts;
{
   pcfrac_m      = m;
   pcfrac_k      = k;
   pcfrac_aborts = aborts;
   return 1;
}
