/*
   This file is part of Numerix.  Numerix is free software; you can
   redistribute it and/or modify it under the terms of the GNU General
   Public License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
   USA 
*/

/* +------------------------------------------------------------------------+
   |                                                                        |
   |                      Entiers de longueur arbitraire                    |
   |                                                                        |
   |                        Exponentiation modulaire                        |
   |                                                                        |
   +------------------------------------------------------------------------+ */

/* M. Quercia, 11/08/2001 */
#include "long_int.h"
#include "long_int-s.h"


                      /* +------------------+
                         |  d <- a^b mod c  |
                         +------------------+ */

/* b >= 0, c <> 0 */
#ifndef have_sz_powmod
void xz(powmod)(entier *a, entier *b, entier *c, entier *d) {
  longueur la = Lg(a), lb = Lg(b), lc = Lg(c), lbuff;
  longueur sa = Signe(a), sc = Signe(c);
  chiffre *aa, *bb, *cc, *x, *y, *z, *buff, *buff_save;
  ndouble u;
  chiffre mask;
  int sh;

  /* exposant = 0 => d = 1 mod c */
  if (lb == 0) {
    if (sc) {
      xn(cpy)(d->val,c->val,lc);
      xn(dec_1)(d->val,lc,1);
      make_head(d,lc,sc);
    } else {
      xn(clear)(d->val,lc);
      d->val[0] = 1;
      d->hd = 1;
    }
    return;
  }

  /* exposant = 1 => d = a mod c */
  if ((lb == 1) && (b->val[0] == 1)) {
    entier *q = xz(alloc_tmp)(max(la-lc+1,1));
    xz(quo_k)(a,c,q,d);
    xz(free)(q);
    return;
  }

  /* nb de bits de décalage pour avoir c[lc-1] >= BASE/2 */
  for (sh = 0, u = c->val[lc-1]; u < BASE/2; u <<= 1, sh++);

  /* mémoire auxilliaire */
  lbuff = 4*lc+4;
  if (la > lc) lbuff += max(la+2 + la-lc+1 - 4*lc-4,lc);
  if (sh)      lbuff += lc;
  buff_save = xn(alloc_tmp)(lbuff);
  buff = buff_save+1;

  bb = b->val;

  /* décale c si besoin */
  if (sh) {
    xn(shl)(c->val,lc,sh,buff);
    cc = buff;
    buff += lc;
  } else { cc = c->val; }

  /* recopie a s'il est plus long que c */
  if (la > lc) {
    xn(quo_k)(a->val,la,c->val,lc,buff+lc,buff);
    aa = buff;
    buff += lc;
    la = lc; while ((la > 0) && (aa[la-1] == 0)) la--;
  } else { aa = a->val; }

  /* zone de travail pour les divisions */
  x = buff; y = buff + 2*lc+2; buff += 4*lc+4;

  /* repère le bit le plus significatif de b */
  bb += lb-1; lb *= HW;
  for (mask=BASE/2; (mask & bb[0]) == 0; mask >>= 1, lb--);

  /* exponentiation dichotomique */
  xn(cpy)(x,aa,la); if (la < lc) xn(clear)(x+la,lc-la);
  lb--; mask >>= 1; if (mask == 0) {bb--; mask = BASE/2;}
  while (lb) {
    xn(sqr_k)(x,lc,y);
    xn(quo_k)(y,2*lc,cc,lc,x,y);
    z = x; x = y; y = z;
    if (mask & bb[0]) {
      xn(mul_k)(x,lc,aa,la,y);
      xn(quo_k)(y,la+lc,cc,lc,x,y);
      z = x; x = y; y = z;
    }
    lb--; mask >>= 1; if (mask == 0) {bb--; mask = BASE/2;}
  }
  
  /* ici, x contient |a|^b mod (c*2^sh) */
  /* dernière réduction modulo c pour compenser le décalage */
  if (sh) {
    xn(shl)(x,lc,sh,x);
    xn(quo_k)(x,lc+1,cc,lc,y,x);
  }

  /* prise en compte des signes de a^b et de c */
  if (((bb[1] % 2) ? sa^sc : sc) && (xn(cmp)(x,lc,NULL,0))) {
    xn(sub)(cc,lc,x,lc,d->val);
  } else {
    xn(move)(d->val,x,lc);
  }

  /* décale le reste et incorpore le signe */
  if (sh) {
    xn(shr)(d->val,lc,sh,d->val);
  }
  make_head(d,lc,sc);

  /* terminé */
  xn(free)(buff_save);
}
#endif

