/*
   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                    |
   |                                                                        |
   |                           Multiplication                               |
   |                                                                        |
   +------------------------------------------------------------------------+ */

/* M. Quercia, 06/02/2001 */

#include "long_int.h"
#include "long_int-s.h"

                         /* +------------+
                            |  c <- a*b  |
                            +------------+ */

#ifndef have_sn_mul_2
void xn(mul_2)(naturel a, longueur la, ndouble b, naturel c) {
  ndouble b0 = b & (BASE-1), b1 = b >> HW, a0,a1,ret0,ret1;
  longueur i;

  if (b1) {
    for (ret0=0, ret1=0, a0=0, i=0; i<la; i++) {
      a1     = a[i];
      ret0  += a1 * b0;
      ret1  += a0 * b1 + (ret0 & (BASE-1));
      c[i]   = ret1;
      a0     = a1;
      ret0 >>= HW;
      ret1 >>= HW;
    }
    ret1  += a0 * b1 + ret0;
    c[i]   = ret1;
    c[i+1] = ret1 >> HW;
  }
  else {
    for (ret0=0, i=0; i<la; i++) {
    ret0 += (ndouble)a[i] * b0;
    c[i] = ret0;
    ret0 >>= HW;
    }
    c[i]   = ret0;
    c[i+1] = 0;
  }
}
#endif

                        /* +---------------+
                           |  idem dans Z  |
                           +---------------+ */

#ifndef have_sz_mul_2
void xz(mul_2)(entier *a, zdouble b, entier *c) {
  longueur la = Lg(a), lc = la+2;

  if ((la == 0) || (b == 0)) {c->hd = 0; return;}
  if (b > 0) {
    xn(mul_2)(a->val, la, b, c->val);
    make_head(c,lc,Signe(a));
  } else {
    xn(mul_2)(a->val, la, -b, c->val);
    make_head(c,lc,SIGN_m^Signe(a));
  }
}
#endif


                /* +-------------------------------+
                   |  c <- a*b, algorithme en n^2  |
                   +-------------------------------+ */

#ifndef have_sn_mul_n2
void xn(mul_n2)(naturel a, longueur la, naturel b, longueur lb, naturel c) {
  ndouble ret,bi;
  longueur i,j;

  /* classe les arguments de sorte que la >= lb */
  if (la < lb) {
    naturel  x  = a;
    longueur lx = la;
    a = b; b = x; la = lb; lb = lx;
  }

  if(lb) {
    /* traite le premier chiffre de b à part pour ne pas initialiser c */
    /* et gagner du temps quand lb = 1                                 */
    for (ret=0, bi=b[0], j=0; j<la; j++) {
    ret += (ndouble)a[j] * bi;
    c[j] = ret;
    ret >>= HW;
    }
    c[j]   = ret;
    /* chiffres suivants */
    for (c++, i=1; i<lb; i++, c++) {
      bi = b[i];
      /* c <- c + a*bi */
      for (ret=0, j=0; j<la; j++) {
	ret += (ndouble)c[j] + (ndouble)a[j] * bi;
	c[j] = ret;
	ret >>= HW;
      }
    c[j]   = ret;
    }

  } else xn(clear)(c,la);

}
#endif

                        /* +---------------+
                           |  idem dans Z  |
                           +---------------+ */

#ifndef have_sz_mul_n2
void xz(mul_n2)(entier *a, entier *b, entier *c) {
  longueur la = Lg(a),    lb = Lg(b);   
  longueur sa = Signe(a), sb = Signe(b);
  longueur l, bfsize;
  entier   *x;
  chiffre  *buff, *aa, *bb;

  /* classe les arguments de sorte que la >= lb */
  if (la < lb) {x=a; a=b; b=x; l=la; la=lb; lb=l;}

  /* traite à part les petites multiplications */
  switch(lb) {
  case 0: c->hd = 0; return;

  case 1:
    xn(mul_2)(a->val,la, (ndouble)b->val[0], c->val);
    make_head(c,la+1,sa^sb);
    return;

  case 2:
    xn(mul_2)(a->val,la, (((ndouble)b->val[1]) << HW) + (ndouble)b->val[0], c->val);
    make_head(c,la+2,sa^sb);
    return;
  }      

  /* recopie les arguments s'ils vont être surchargés */
  if (c==a) bfsize = la; else if (c==b) bfsize = lb; else bfsize=0;
  if (bfsize) buff = xn(alloc_tmp)(bfsize); else buff = NULL;

  if (c==a) {
    xn(cpy)(buff,a->val,la);
    aa = buff;
    if (c==b) bb = aa; else bb = b->val;
  } else if (c==b) {
    xn(cpy)(buff,b->val,lb);
    bb = buff;
    aa = a->val;
  } else {
    aa = a->val;
    bb = b->val;
  }

  /* effectue la multiplication */
  xn(mul_n2)  (aa,la,bb,lb,c->val);
  make_head(c,la+lb,sa^sb);

  if (bfsize) xn(free)(buff);

}
#endif

                /* +------------------------------+
                   |  Décomposition de Karatsuba  |
                   +------------------------------+ */

#ifndef have_sn_karamul
void xn(karamul)(naturel a, longueur la, naturel b, longueur lb, naturel c) {
  longueur l0,l1,l2,lu,lv;
  naturel buff,c0,c1,c2,c3;
  ndouble x,y;
  int s;

  /* classe les arguments de sorte que la >= lb */
  if (la < lb) {buff = a; a = b; b = buff; l0 = la; la = lb; lb = l0;}
  if (lb < klim) {xn(mul_n2)(a,la,b,lb,c); return;}

  /* si lb <= la/2, distribue par tranches de lb chiffres */
  l0 = (la+1)/2; l1 = la-l0; l2 = lb-l0;
  if (l2 <= 0) {
    xn(karamul)(a+lb,la-lb,b,lb,c+lb);
    buff = xn(alloc_tmp)(2*lb);
    xn(karamul)(a,lb,b,lb,buff);
    xn(cpy)(c,buff,lb);
    xn(inc)(c+lb,la,buff+lb,lb);
    xn(free)(buff);
    return;
  }

  c0 = c; c1 = c0+l0; c2 = c1+l0; c3 = c2+l0;

  /*
      +------+------+       +------+------+       +------+------+------+------+
  a = |  a1  |  a0  |   b = |  b1  |  b0  |   c = |  c3  |  c2  |  c1  |  c0  |
      +------+------+       +------+------+       +------+------+------+------+
       <-l1-> <-l0->         <-l2-> <-l0->         <---l1+l2---> <-l0-> <-l0-> 
  */

  lu = xn(cmp)(a,l0, a+l0,l1);                 /* u <- |a0-a1| */
  if      (lu > 0) {xn(sub)(a,lu, a+l0, (l1 < lu) ? l1 : lu, c); s = 0;}
  else if (lu < 0) {lu = -lu; xn(sub)(a+l0,lu, a,lu, c);         s = 1;}
#ifdef useless_init
  else s = 0;
#endif

  if (lu) {
    lv = xn(cmp)(b,l0, b+l0,l2);               /* v <- |b0-b1| */
    if      (lv > 0) {xn(sub)(b,lb, b+l0, (l2 < lv) ? l2 : lv, c+lu);}
    else if (lv < 0) {lv = -lv; xn(sub)(b+l0,lv, b,lv, c+lu); s = 1-s;}
    else lu = 0;
  }
#ifdef useless_init
  else lv = 0;
#endif

  if (lu) {                         /* buff <- |a0-a1|*|b0-b1| */
    buff = xn(alloc_tmp)(lu+lv);
    xn(karamul)(c,lu, c+lu,lv, buff);
  }
#ifdef useless_init
  else buff = NULL;
#endif

  xn(karamul)(a,   l0, b,   l0, c0);           /* c1:c0 <- a0*b0 */
  xn(karamul)(a+l0,l1, b+l0,l2, c2);           /* c3:c2 <- a1*b1 */

  x = xn(inc)(c1, l0, c2, l0);                 /* c1 <- c1 + c2  */
  if (l1+l2 > l0) {
    y = c3[0];
    xn(add)(c3, l1+l2-l0, c1, l0, c2);         /* c2 <- c1 + c2 + c3 */
    xn(inc)(c1, l1+l2+l0, c0, l0);             /* c1 <- c0 + c1 + c2 */
    xn(inc_1)(c2, l1+l2,    x);                /* propage les retenues */
    xn(inc_1)(c3, l1+l2-l0, x);
    xn(inc_1)(c3, l1+l2-l0, y);
  } else {
    xn(cpy)(c2, c1, l0);                       /* c2 <- c1 + c2 + c3 */
    xn(inc)(c1, l1+l2+l0, c0, l0);             /* c1 <- c0 + c1 + c2 */
    xn(inc_1)(c2, l1+l2,    x);                /* propage les retenues */
  }

  /* ajoute ou retranche |a0-a1|*|b0-b1| */
  if (lu) {
    if (s) xn(inc)(c1,l0+l1+l2, buff,lu+lv);
    else   xn(dec)(c1,l0+l1+l2, buff,lu+lv);
    xn(free)(buff);
  }
}
#else
void xn(karamul)(naturel a, longueur la, naturel b, longueur lb, naturel c);
#endif


              /* +-----------------------------------+
                 |  c <- a*b, multiplication rapide  |
                 +-----------------------------------+ */

#ifndef have_sn_mul_k
void xn(mul_k)(naturel a, longueur la, naturel b, longueur lb, naturel c) {

  /* classe les arguments de sorte que la >= lb */
  if (la < lb) {
    naturel  x  = a;
    longueur lx = la;
    a = b; b = x; la = lb; lb = lx;
  }

  /* sélectionne un algorithme adapté à la taille des opérandes. */
  if      (lb < klim)  xn(mul_n2)  (a,la,b,lb,c);
  else if (la < flim)  xn(karamul) (a,la,b,lb,c);
  else                 xn(fftmul)  (a,la,b,lb,c);

}
#endif

              /* +----------------------------------+
                 |  c <- a*b, longueur(c) >= la+lb  |
                 +----------------------------------+ */

#ifndef have_sz_mul_k
void xz(mul_k)(entier *a, entier *b, entier *c) {
  longueur la = Lg(a),    lb = Lg(b);   
  longueur sa = Signe(a), sb = Signe(b);
  longueur l, bfsize;
  entier   *x;
  chiffre  *buff, *aa, *bb;

  /* classe les arguments de sorte que la >= lb */
  if (la < lb) {x=a; a=b; b=x; l=la; la=lb; lb=l;}

  /* traite à part les petites multiplications */
  switch(lb) {
  case 0: c->hd = 0; return;

  case 1:
    xn(mul_2)(a->val,la, (ndouble)b->val[0], c->val);
    make_head(c,la+1,sa^sb);
    return;

  case 2:
    xn(mul_2)(a->val,la, (((ndouble)b->val[1]) << HW) + (ndouble)b->val[0], c->val);
    make_head(c,la+2,sa^sb);
    return;
  }      

  /* multiplication par FFT -> pas besoin de recopier les arguments */
  if ((lb >= klim) && (la >= flim)) xn(fftmul)  (a->val,la,b->val,lb,c->val);
  else {

    /* recopie les arguments s'ils vont être surchargés */
    if (c==a) bfsize = la; else if (c==b) bfsize = lb; else bfsize=0;
    if (bfsize) buff = xn(alloc_tmp)(bfsize); else buff = NULL;

    if (c==a) {
      xn(cpy)(buff,a->val,la);
      aa = buff;
      if (c==b) bb = aa; else bb = b->val;
    } else if (c==b) {
      xn(cpy)(buff,b->val,lb);
      bb = buff;
      aa = a->val;
    } else {
      aa = a->val;
      bb = b->val;
    }

    /* effectue la multiplication */
    if  (lb < klim)  xn(mul_n2)  (aa,la,bb,lb,c->val);
    else             xn(karamul) (aa,la,bb,lb,c->val);

    if (bfsize) xn(free)(buff);
  }
  make_head(c,la+lb,sa^sb);

}
#endif

