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

   Srsieve specialised for the Sierpinski/Riesel Base 5 projects.

   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 <stdio.h>
#include <time.h>
#include <getopt.h>
#include "sr5sieve.h"
#include "version.h"
#include "arithmetic.h"

#define NAME "sr" XSTR(BASE) "sieve"
#define DESC "A sieve for the Sierpinski/Riesel Base " XSTR(BASE) " projects"
#define DATA_FILE_NAME "sr" XSTR(BASE) "data.txt"
#define WORK_FILE_NAME "sr" XSTR(BASE) "work.txt"

#if (BASE==5)
#define FACTORS_EMAIL "base5.factors@yahoo.com"
#endif

#if (BASE==2)
#define SOB_DAT "SoB.dat"
#define RIESEL_DAT "riesel.dat"
#endif

static void banner(void)
{
  printf("%s %d.%d.%d -- %s.\n", NAME, MAJOR_VER, MINOR_VER, PATCH_VER, DESC);
}

static void help(void)
{
  printf("Usage: %s [OPTION ...]\n", NAME);
  printf(" -d --delete K         "
         "Delete the sequence K*%"PRIu32"^n+/-1 from `%s'.\n",
         BASE,DATA_FILE_NAME);
#if NEWPGEN_OPT
  printf(" -g --newpgen K N0 N1  "
         "Generate a NewPGen file for K*%"PRIu32"^n+/-1 with N0 <= n < N1.\n",
         BASE);
#endif
#if (BASE==2)
  printf(" -s --sierpinski       "
         "Sieve sequences in dat format file '%s'\n",SOB_DAT);
  printf(" -r --riesel           "
         "Sieve sequences in dat format file '%s'\n",RIESEL_DAT);
#endif
  printf(" -h --help             "
         "Print this help.\n\n");

  printf("Without arguments (normal usage), sieve sequences in `%s' for\n",
         DATA_FILE_NAME);
  printf("primes p in pmin <= p <= pmax, where <pmin>,<pmax> is a pair of"
         " integers\n");
  printf("(in multiples of 10^9) read from `%s'.\n", WORK_FILE_NAME);

  exit(0);
}

#if (BASE==2)
static const char *short_opts = "d:g:srh";
#else
static const char *short_opts = "d:g:h";
#endif
static const struct option long_opts[] =
  {
    {"delete",  required_argument, 0, 'd'},
#if NEWPGEN_OPT
    {"newpgen", required_argument, 0, 'g'},
#endif
#if (BASE==2)
    {"sierpinski", required_argument, 0, 's'},
    {"riesel",     required_argument, 0, 'r'},
#endif
    {"help",    no_argument,       0, 'h'},
    {0, 0, 0, 0}
  };

static time_t start_date;
static uint64_t work_pmin;
static int read_work_file(void);
static void update_work_file(void);

int main(int argc, char **argv)
{
  int opt_c, opt_ind, want_help = 0;
  uint32_t k_to_delete = 0;
#if NEWPGEN_OPT
  uint32_t newpgen_k = 0, n0 = 0, n1 = UINT32_MAX;
#endif
#if (BASE==2)
  int riesel_dat_opt = 0, sob_dat_opt = 0;
#endif

  while ((opt_c = getopt_long(argc,argv,short_opts,long_opts,&opt_ind)) != -1)
    switch (opt_c)
    {
      case 'd':
        k_to_delete = strtoul(optarg,NULL,0);
        break;
#if NEWPGEN_OPT
      case 'g':
        newpgen_k = strtoul(optarg,NULL,0);
        break;
#endif
#if (BASE==2)
      case 'r':
        riesel_dat_opt = 1;
        break;
      case 's':
        sob_dat_opt = 1;
        break;
#endif
      case 'h':
        want_help = 1;
        break;
      default:
        return 1;
    }

  banner();

  if (want_help)
    help();

#if NEWPGEN_OPT
  if (newpgen_k > 0)
  {
    if (optind < argc)
      n0 = strtoul(argv[optind++],NULL,0);
    if (optind < argc)
      n1 = strtoul(argv[optind++],NULL,0);
  }
#endif

  if (optind < argc)
    error("Unhandled argument %s.", argv[optind]);

#if (BASE==2)
  if (sob_dat_opt)
  {
    report(0,"Reading `%s' ...", SOB_DAT);
    if (!read_dat_file(SOB_DAT,1))
      error("Cannot open `%s'.", SOB_DAT);
  }
  if (riesel_dat_opt)
  {
    report(0,"Reading `%s' ...", RIESEL_DAT);
    if (!read_dat_file(RIESEL_DAT,-1))
      error("Cannot open `%s'.", RIESEL_DAT);
  }
  if (!sob_dat_opt && !riesel_dat_opt)
  {
    report(0,"Reading project data file `%s' ...", DATA_FILE_NAME);
    if (!read_abcd_file(DATA_FILE_NAME))
      error("Cannot open project data file `%s'.", DATA_FILE_NAME);
  }
#else
  report(0,"Reading project data file `%s' ...", DATA_FILE_NAME);
  if (!read_abcd_file(DATA_FILE_NAME))
    error("Cannot open project data file `%s'.", DATA_FILE_NAME);
#endif

#if NEWPGEN_OPT
  if (newpgen_k > 0)
  {
    uint32_t i;

    for (i = 0; i < seq_count; i++)
      if (SEQ[i].k == newpgen_k)
        break;
    if (i == seq_count)
      error("%"PRIu32"*%"PRIu32"^n+/-1 not found in %s.",
            newpgen_k, BASE, DATA_FILE_NAME);
    write_newpgen_k(i,n0,n1);
    exit(0);
  }
#endif

  if (k_to_delete)
  {
    overwrite_abcd(p_min,DATA_FILE_NAME,k_to_delete);
    exit(0);
  }

  finish_candidate_seqs();

  while (read_work_file())
  {
    start_date = time(NULL);
    work_pmin = p_min;
    p_min = read_checkpoint(p_min,p_max);
    notify_event(sieve_parameters_changed);
    sieve();
#ifdef FACTORS_EMAIL
    if (factor_count > 0)
      report(1,"Please send `%s' to %s.", factors_file_name, FACTORS_EMAIL);
#endif
    update_work_file();
  }

  return 0;
}

#define RANGE_BLOCK 1000000000
static char factors_txt[24];
static int read_work_file(void)
{
  uint64_t p0, p1;
  FILE *work_file;
  int ret;

  report(0,"Reading project work file `%s' ...",WORK_FILE_NAME);
  work_file = xfopen(WORK_FILE_NAME,"r",error);

  ret = fscanf(work_file, "%"SCNu64",%"SCNu64,&p0,&p1);
  xfclose(work_file,WORK_FILE_NAME);
  switch (ret)
  {
    case EOF:
      report(1,"No more work in `%s'.",WORK_FILE_NAME);
      return 0;

    case 2:
      if (p0 >= p1)
        error("Bad range %"PRIu64",%"PRIu64" in `%s'.",p0,p1,WORK_FILE_NAME);
      if (p0 <= UINT32_MAX/RANGE_BLOCK)
        error("Range start %"PRIu64" too low in `%s'.",p0,WORK_FILE_NAME);
      if (p1 > MOD64_GREATEST_PRIME/RANGE_BLOCK)
        error("Range end %"PRIu64" too high in `%s'.",p1,WORK_FILE_NAME);

      sprintf(factors_txt,"factors%"PRIu64".txt",p0);
      factors_file_name = factors_txt;
      p_min = p0*RANGE_BLOCK;
      p_max = p1*RANGE_BLOCK;
      return 1;

    case 0:
    case 1:
      error("Malformed first line in `%s'.",WORK_FILE_NAME);

    default:
      error("Error while reading `%s'.",WORK_FILE_NAME);
  }
}

#define MAX_WORK_LINES 1000
#define MAX_LINE_LENGTH 80
static void update_work_file(void)
{
  FILE *work_file;
  char *lines[MAX_WORK_LINES];
  int i, j;

  work_file = xfopen(WORK_FILE_NAME,"r",error);

  for (i = 0; i < MAX_WORK_LINES; i++)
  {
    lines[i] = xmalloc(MAX_LINE_LENGTH);
    if (fgets(lines[i],MAX_LINE_LENGTH,work_file) == NULL)
    {
      free(lines[i]);
      break;
    }
  }

  xfclose(work_file,WORK_FILE_NAME);
  work_file = xfopen(WORK_FILE_NAME,"w",error);

  for (j = 1; j < i; j++)
    fputs(lines[j],work_file);
  xfclose(work_file,WORK_FILE_NAME);

  for (j = 0; j < i; j++)
    free(lines[j]);
}

#define STATUS_STRFTIME_FORMAT "ETA %d %b %H:%M"
void print_status(uint64_t p, uint32_t p_per_sec)
{
  char buf[32];
  time_t current_date, finish_date;
  double done, range_done;

  done = (double)(p-p_min)/(p_max-work_pmin);

  if (done == 0.0)
    return;

  range_done = (double)(p-work_pmin)/(p_max-work_pmin);
  current_date = time(NULL);

  finish_date = current_date+(1.0-range_done)/done*(current_date-start_date);

  strftime(buf,sizeof(buf),STATUS_STRFTIME_FORMAT,localtime(&finish_date));

  report(0,"p=%"PRIu64", %"PRIu32" p/sec, %"PRIu32" factor%s, %.2f%% done, %s",
         p,p_per_sec,factor_count,plural(factor_count),100.0*range_done,buf);
}

void start_srsieve(void)
{
  factor_count = 0;
  logger("%s started: %"PRIu32" <= n <= %"PRIu32", %"PRIu64" <= p <= %"PRIu64,
         NAME, n_min, n_max, p_min, p_max);
}

void finish_srsieve(const char *reason, uint64_t p)
{
  if (p >= p_max) /* Sieving job completed */
    remove_checkpoint();
  else
    write_checkpoint(p);
  logger("%s stopped: at p=%"PRIu64" because %s.", NAME, p, reason);
}
