/* subseq.c -- (C) Geoffrey Reynolds, June 2006.

   Base b sequences in n=qm+r where n0 < n < n1 are represented as a number
   of base b^Q subsequences in m where n0/Q < m < n1/Q.

   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 <stdlib.h>
#include "sr5sieve.h"
#include "bitmap.h"

subseq_t *SUBSEQ = NULL;
uint32_t subseq_count = 0;
uint32_t subseq_Q = 0;

static uint32_t m_low;
static uint32_t m_high;

static int subseq_clear_m(uint32_t subseq, uint32_t m)
{
  assert(subseq < subseq_count);

  if (m < m_low || m > m_high)
    return 0;

  return clear_bit(SUBSEQ[subseq].M, m-m_low);
}

void make_subseqs(void)
{
  uint32_t i, j, r, n, Q, s, s_count;
  subseq_t *tmp;
  unsigned parity;

#ifndef NDEBUG
  uint32_t t_count = 0, old_count = 0;
  unsigned parity_count[3] = {0,0,0};
#endif

  Q = find_best_Q(LIMIT_BASE,&s_count);
  tmp = xmalloc(s_count*sizeof(subseq_t));
  m_low = n_min/Q;
  m_high = n_max/Q;

  for (i = 0, s = 0; i < seq_count; i++)
  {
    uint32_t nmin[Q], subseq[Q];

    SEQ[i].first = s;

    for (j = 0; j < Q; j++)
      nmin[j] = UINT32_MAX;

    parity = SEQ[i].N[0]%2;
    for (j = 0; j < SEQ[i].ncount; j++)
    {
      n = SEQ[i].N[j];
      r = n % Q;
      nmin[r] = MIN(nmin[r],n);
      if (n%2 != parity)
#if ALLOW_MIXED_PARITY_SEQUENCES
        parity = 2;
#else
        error("Sequence %s has mixed parity terms n0=%"PRIu32", n1=%"PRIu32".",
            seq_str(i),SEQ[i].N[0],n);
#endif
    }
#ifndef NDEBUG
    parity_count[parity]++;
#endif

    for (r = 0; r < Q; r++)
      if (nmin[r] != UINT32_MAX)
      {
        tmp[s].seq = i;
        tmp[s].d = r;
        tmp[s].M = make_bitmap(m_high-m_low+1);
        tmp[s].mcount = 0;
        subseq[r] = s++;
      }

    assert (s <= s_count);

    SEQ[i].last = s-1;

    for (j = 0; j < SEQ[i].ncount; j++)
    {
      n = SEQ[i].N[j];
      r = n % Q;
      set_bit(tmp[subseq[r]].M, n/Q-m_low);
      tmp[subseq[r]].mcount++;
    }

    free(SEQ[i].N);
#ifndef NDEBUG
    old_count += SEQ[i].ncount;
#endif

    generate_legendre_lookup_table(i,parity);
  }

  assert (s == s_count);

#ifndef NDEBUG
  report(1,"Built (-ckb^n/p) tables for %u even, %u odd, %u mixed parity"
         " sequences.",parity_count[0],parity_count[1],parity_count[2]);
#endif

#ifndef NDEBUG
  for (i = j = 0; i < seq_count; i++)
    j += SEQ[i].mod;
  report(1,"Used %"PRIu32" Kb for Legendre lookup tables, %"
         PRIu32" Kb for subsequence bitmaps.",
         j/8/1024, s_count*(m_high-m_low+1)/8/1024);
#endif

  report(1,"Split %"PRIu32" base %"PRIu32" sequence%s into %"PRIu32
         " base %"PRIu32"^%"PRIu32" subsequence%s.", seq_count,
         BASE, plural(seq_count), s_count, BASE, Q, plural(s_count));

  subseq_Q = Q;
  SUBSEQ = tmp;
  subseq_count = s_count;

#ifndef NDEBUG
  for (i = 0, t_count = 0; i < subseq_count; i++)
    t_count += SUBSEQ[i].mcount;
  assert(t_count == old_count);
#endif
}

static void report_factor(uint32_t subseq, uint32_t n, uint64_t p)
{
  uint32_t k = SEQ[SUBSEQ[subseq].seq].k;
  int32_t c = SEQ[SUBSEQ[subseq].seq].c;

#if CHECK_FACTORS
  if (!is_factor(k,c,n,p))
    error("%"PRIu64" DOES NOT DIVIDE %s.",p,kbnc_str(k,n,c));
#endif

  factor_count++;
  SUBSEQ[subseq].mcount--;
  save_factor(k,c,n,p);
  report(1,"%"PRIu64" | %s",p,kbnc_str(k,n,c));
  notify_event(factor_found);
}

#if REPORT_DUPLICATES
static void report_duplicate_factor(uint32_t seq, uint32_t n, uint64_t p)
{
  uint32_t k = SEQ[seq].k;
  int32_t c = SEQ[seq].c;

#if CHECK_FACTORS
  if (!is_factor(k,c,n,p))
    error("%"PRIu64" DOES NOT DIVIDE %s (duplicate).",p,kbnc_str(k,n,c));
#endif

  if (n <= n_max)
  {
    report(1,"%"PRIu64" | %s (duplicate)",p,kbnc_str(k,n,c));
    notify_event(factor_found);
  }
}
#endif /* REPORT_DUPLICATES */

/* eliminate a single term n=Qm+d for this subsequence.
*/
void eliminate_term(uint32_t subseq, uint32_t m, uint64_t p)
{
  assert(subseq < subseq_count);

  if (subseq_clear_m(subseq,m))
    report_factor(subseq,m*subseq_Q+SUBSEQ[subseq].d,p);
#if REPORT_DUPLICATES
  else
    report_duplicate_factor(SUBSEQ[subseq].seq,m*subseq_Q+SUBSEQ[subseq].d,p);
#endif /* REPORT_DUPLICATES */
}
