/* This file is part of the gf2x library.

   Copyright 2007, 2008, 2009
   Richard Brent, Pierrick Gaudry, Emmanuel Thome', Paul Zimmermann

   This program 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; see the file COPYING.  If not, write to the Free
   Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
   02111-1307, USA.
*/

/* General Toom_Cook multiplication, calls KarMul, Toom3Mul, Toom3WMul
   or Toom4Mul depending on which is expected to be the fastest. */

#include <limits.h>
#include <string.h>

#include "gf2x.h"
#include "gf2x/gf2x-impl.h"

/* We need gf2x_addmul_1_n */
#include "gf2x/gf2x-small.h"

#include "gf2x/gf2x-config.h"

short best_tab[GF2X_TOOM_TUNING_LIMIT] = GF2X_BEST_TOOM_TABLE;
short best_utab[GF2X_TOOM_TUNING_LIMIT] = GF2X_BEST_UTOOM_TABLE;

/* Returns 0 for KarMul, 1 for Toom3Mul, 2 for Toom3WMul, 3 for Toom4Mul
   depending on which is predicted to be fastest for the given degree n.

   RPB, 20070511 */

short gf2x_best_toom(unsigned int n)
{
// GF2X_BEST_TOOM_TABLE should be generated by the tuning program tunetoom.
//
// The n-th entry in the list gives the code for the fastest algorithm for
// input size n.  For example:
// #define GF2X_BEST_TOOM_TABLE {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,2,1,2,2,1,2}
// would be reasonable if Toom3Mul was fastest for n = 18, 21, 24.

    if (n < GF2X_MUL_TOOMW_THRESHOLD)
	return GF2X_SELECT_KARA;		// KarMul

    if (n >= GF2X_MUL_TOOM4_ALWAYS_THRESHOLD)
	return GF2X_SELECT_TC4;		// Toom4Mul

    /* This would be a tuning bug */
    ASSERT (n <= GF2X_TOOM_TUNING_LIMIT);

    return best_tab[n - 1];	// Return table entry
}

short gf2x_best_utoom(unsigned int n)
{
// GF2X_BEST_UTOOM_TABLE should be generated by the tuning program tuneutoom.
//
// The n-th entry in the list gives the code for the fastest algorithm for
// input size n.  0 means the obvious splitting algorithm and 1 means
// Toom3uMul.

    if (n < GF2X_MUL_TOOMU_THRESHOLD)
	return GF2X_SELECT_UNB_DFLT;		// Default

    if (n >= GF2X_MUL_TOOMU_ALWAYS_THRESHOLD)
	    return GF2X_SELECT_UNB_TC3U;

    /* This would be a tuning bug */
    ASSERT (n <= GF2X_TOOM_TUNING_LIMIT);

    return best_utab[n - 1];	// Return table entry
}

/* Returns the worst-case space (in words) needed by the Toom-Cook routines
   KarMul, Toom3Mul, Toom3WMul, Toom4Mul.

  Copyright 2007 Richard P. Brent.
*/

/*
 The memory sp(n) necessary for Toom3WMul satisfies
 sp(n) <== (n lt 8) ? 19 : 8*(floor(n/3) + 3) + sp(floor(n/3) + 2),
 sp(7) <= 19.

 It is assumed that KarMul is called for n < 8 <= GF2X_MUL_TOOMW_THRESHOLD
 and requires space KarMulMem(n) <= 4*ceil(n/2) + KarMulMem(ceil(n/2)),
 KarMulMem(7) <= 19.  The memory for Toom3Mul and Toom4Mul is no larger
 than that for Toom3WMul.

 Note: KarMulMem(7) is now 0, but would increase if GF2X_MUL_KARA_THRESHOLD
       were reduced. We have not changed gf2x_ToomSpace as a small overestimate
       in space is not harmful.
*/

#if (GF2X_MUL_KARA_THRESHOLD < 5)
#error "GF2X_MUL_KARA_THRESHOLD assumed to be at least 5"
#endif

#if (GF2X_MUL_TOOMW_THRESHOLD < 8)
#error "GF2X_MUL_TOOMW_THRESHOLD assumed to be at least 8"
#endif

long gf2x_toomspace(long n)
{
    long sp;
    long low = (GF2X_MUL_KARA_THRESHOLD < GF2X_MUL_TOOMW_THRESHOLD) ?
	GF2X_MUL_KARA_THRESHOLD : GF2X_MUL_TOOMW_THRESHOLD;
    if (n < low)
	return 0;
    sp = 19;			// KarMulMem (7) <= 19
    while (n >= 8) {
	n = n / 3 + 2;
	sp += 8 * (n + 1);
    }
    return sp;
}

/* Returns upper bound on space required by Toom3uMul (c, a, sa, b, stk):
   2*sa + 32 + gf2x_toomspace(sa/4 + 4) */

long gf2x_toomuspace(long sa)
{
    if (sa < GF2X_MUL_TOOMU_THRESHOLD)
	return 0;
    else
	return 2 * sa + 32 + gf2x_toomspace(sa / 4 + 4);
}
/*   stk should point to a block of sufficient memory for any of these
     routines (gf2x_toomspace(n) <= 5*n+17 words is enough).
     Output c must not overlap inputs a, b.
     The output c is a*b (where a, b and c are in GF(2)[x]).
     RPB, 20070510 */
void gf2x_mul_toom(unsigned long *c, const unsigned long *a,
			    const unsigned long *b, long n,
			    unsigned long *stk)
{
    while (n && a[n - 1] == 0 && b[n - 1] == 0) {
	c[2 * n - 1] = 0;
	c[2 * n - 2] = 0;
	n--;
    }

    assert(c != a);
    assert(c != b);

    switch (gf2x_best_toom(n)) {
    case 0:
	gf2x_mul_kara(c, a, b, n, stk);
	break;
        /* 1 2 3 are GPL'ed code */
    case 1:
	gf2x_mul_tc3(c, a, b, n, stk);
	break;
    case 2:
	gf2x_mul_tc3w(c, a, b, n, stk);
	break;
    case 3:
	gf2x_mul_tc4(c, a, b, n, stk);
	break;
    }
}

/* Version of Karatsuba multiplication with minimal temporary storage
   sp(n) = 3*ceil(n/2) + sp(ceil(n/2)) = 3n + O(log n) words.
   RPB, 20070522 */

void gf2x_mul_kara(unsigned long * c, const unsigned long * a, const unsigned long * b,
	      long n, unsigned long * stk)
{
    unsigned long t;
    unsigned long *aa, *bb, *cc;
    long j, d, n2;

    assert(c != a);
    assert(c != b);

#if 0
    if (n <= 0)
    {				/* if turned on this test shows that calls with n == 0 */
	/* do occur (e.g from tunefft, FFT(19683)), but don't  */
	/* seem to be harmful if mul_basecase_n just returns.  */
	printf("\nWarning: n %ld in call to KarMul\n", n);
	fflush(stdout);
    }
#endif

    if (n < GF2X_MUL_KARA_THRESHOLD) {
	gf2x_mul_basecase(c, a, n, b, n);
	return;
    }

    n2 = (n + 1) / 2;		/* ceil(n/2) */
    d = n & 1;			/* 2*n2 - n = 1 if n odd, 0 if n even */
    aa = stk;			/* Size n2   */
    bb = aa + n2;		/* Size n2   */
    cc = bb + n2;		/* Size n2   */

    stk = cc + n2;		/* sp(n) = 3*ceil(n/2)) + sp(ceil(n/2)) */

    const unsigned long *a1 = a + n2;	/* a[n2] */
    const unsigned long *b1 = b + n2;	/* b[n2] */
    unsigned long *c1 = c + n2;		/* c[n2]   */
    unsigned long *c2 = c1 + n2;	/* c[2*n2] */
    unsigned long *c3 = c2 + n2;	/* c[3*n2] */

    gf2x_mul_kara(c, a, b, n2, stk);	/* Low */

    gf2x_mul_kara(c2, a1, b1, n2 - d, stk);	/* High */

    for (j = 0; j < n2 - d; j++) {
	aa[j] = a[j] ^ a1[j];
	bb[j] = b[j] ^ b1[j];
	cc[j] = c1[j] ^ c2[j];
    }
    for (; j < n2; j++) {	/* Only when n odd */
	aa[j] = a[j];
	bb[j] = b[j];
	cc[j] = c1[j] ^ c2[j];
    }

    gf2x_mul_kara(c1, aa, bb, n2, stk);	/* Middle */

    for (j = 0; j < n2 - 2 * d; j++) {
	t = cc[j];
	c1[j] ^= t ^ c[j];
	c2[j] ^= t ^ c3[j];
    }
    for (; j < n2; j++) {	/* Only when n odd */
	c1[j] ^= cc[j] ^ c[j];
	c2[j] ^= cc[j];
    }
}

