/* legendre.c -- (C) Geoffrey Reynolds, September 2006.

   Generate lookup tables to replace the Legendre(-ckb^n,p) function.

   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.
*/

#include <assert.h>
#include <inttypes.h>
#include <math.h>
#include "sr5sieve.h"
#include "bitmap.h"

/* Return the least positive d such that n/d is a square.
 */
static uint32_t core(uint32_t n)
{
  uint32_t c, q, r;

  assert(n > 0);

  for (r = n, c = 1, q = 2; q <= r; q = (q + 1) | 1)
    if (n % q == 0)
    {
      do
      {
        n /= q;
        if (n % q != 0)
          c *= q;
        else
          n /= q;
      }
      while (n % q == 0);
      r = sqrt(n);
      if (r*r == n)
        return c;
    }

  return c * n;
}

/* Return (a/p) if gcd(a,p)==1, 0 otherwise.
 */
static int legendre(int32_t a, uint32_t p)
{
  uint32_t x, y, t;
  int sign;

  if (a < 0)
    x = -a, sign = (p % 4 == 1) ? 1 : -1;
  else
    x = a, sign = 1;

  for (y = p; x > 0; x %= y)
  {
    for ( ; x % 2 == 0; x /= 2)
      if (y % 8 == 3 || y % 8 == 5)
        sign = -sign;

    t = x, x = y, y = t;

    if (x % 4 == 3 && y % 4 == 3)
      sign = -sign;
  }

  return (y == 1) ? sign : 0;
}

/* Set .mod and .map fields for sequence k*b^n+c so that bit (p/2)%mod of
   map is set if and only if:
   (-ck/p)=1 for sequences with all n even,
   (-bck/p)=1 for sequences with all n odd,
   (-ck/p)=1 or (-bck/p)=1 for sequences with both odd and even n.
*/
void generate_legendre_lookup_table(uint32_t seq, unsigned parity)
{
  int32_t r;
  uint32_t i, m;
  uint_fast32_t *B;

  report(0,"Building Legendre symbol lookup table for %s ...",seq_str(seq));

  m = core(SEQ[seq].k);
  assert(m < INT32_MAX/BASE);
  r = m * (-SEQ[seq].c);

  switch (parity)
  {
    default:
    case 1: /* odd n, test for (-bck/p)==1 */
      r *= BASE;
      m *= BASE;
      /* Fall through */
    case 0: /* even n, test for (-ck/p)==1 */
      if ((r < 0 && (-r) % 4 != 3) || (r > 0 && r % 4 != 1))
        m *= 2;
      B = make_bitmap(m);
      for (i = 0; i < m; i++)
        if (legendre(r,2*i+1) == 1)
          set_bit(B,i);
      break;
#if ALLOW_MIXED_PARITY_SEQUENCES
    case 2: /* even and odd n, test for (-ck/p)==1 or (-bck/p)==1 */
      m = m*2*BASE;
      B = make_bitmap(m);
      for (i = 0; i < m; i++)
        if (legendre(r,2*i+1) == 1 || legendre(r*BASE,2*i+1) == 1)
          set_bit(B,i);
      break;
#endif
  }

  SEQ[seq].mod = m;
  SEQ[seq].map = B;
}

#if USE_MOD_PRODUCTS
static uint32_t gcd(uint32_t a, uint32_t b)
{
  uint32_t c;

  while (b > 0)
  {
    c = a % b;
    a = b;
    b = c;
  }

  return a;
}

uint32_t *mod_product, mod_product_count;

/* Set SEQ[i].prod = j such that SEQ[i].mod is a divisor of mod_product[j]
   using the minimum number of j. Ensure each product is >= 2^30 by
   multiplying by a power of 2 if necessary. This ensures that (p/2)%prod
   can be computed using a single div instruction without overflow for p up
   to 2^62. Uses a simple greedy algorithm for now, but could be improved.
*/
void generate_mod_products(void)
{
  uint32_t mod[seq_count], prod[seq_count];
  uint32_t g, h, i, j, k, plen, gmax;

  for (i = 0; i < seq_count; i++)
    mod[i] = SEQ[i].mod;

  for (plen = i = 0; i < seq_count; i++)
  {
    /* Find the greatest remaining mod */
    for (j = 0, k = 1; k < seq_count; k++)
      j = mod[j] > mod[k] ? j : k;

    /* find the prod that maximises l = lcm(prod,mod) */
    for (gmax = h = k = 0; k < plen; k++)
      if ((g = gcd(mod[j],prod[k])) > gmax && mod[j]/g <= UINT32_MAX/prod[k])
        gmax = g, h = k;

    if (gmax > 0) /* found an existing prod */
    {
      prod[h] *= mod[j]/gmax; /* prod[h] = lcm(mod[j],prod[h]) */
      SEQ[j].prod = h;
    }
    else /* start a new prod */
    {
      prod[k] = mod[j];
      SEQ[j].prod = k;
      plen++;
    }

    mod[j] = 0;
  }

  mod_product = xmalloc(plen*sizeof(uint32_t));
  mod_product_count = plen;
  for (i = 0; i < plen; i++)
  {
    while (prod[i] < (1 << 30))
      prod[i] *= 2;

    mod_product[i] = prod[i];
  }
}

#endif /* USE_MOD_PRODUCTS */
