/* events.c -- (C) Geoffrey Reynolds, May 2006.

   Interrupts, alarms, and other events that cannot be handled part way
   through a bsgs() iteration.

   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 <string.h>
#include <signal.h>
#include "sr5sieve.h"

/* Start with an initial event: initialisation.
 */
volatile int event_happened = 1;

/* Setting an entry in events[] has to be atomic, so we cannot use a bitmap.
 */
static uint_fast8_t events[last_event] = {1};

void notify_event(event_t event)
{
  assert(event < last_event);

  /* The order here is important, see note in process_events() 
   */
  events[event] = 1;
  event_happened = 1;
}

static int clear_event(event_t event)
{
  int ret;

  assert(event < last_event);

  ret = events[event];
  events[event] = 0;

  return ret;
}

static uint64_t prev_prime;
static uint32_t last_report_time;  /* in milliseconds */
static uint32_t next_report_due;   /* in seconds */
static uint32_t next_save_due;     /* in seconds */

static void init_progress_report(uint64_t current_prime)
{
  prev_prime = current_prime;
  last_report_time = millisec_clock();

  next_save_due = sec_clock() + SAVE_PERIOD;
  next_report_due = sec_clock() + REPORT_PERIOD;
}

static void progress_report(uint64_t p)
{
  uint32_t current_time = millisec_clock();

  if (current_time == last_report_time)
    return;

  print_status(p, (p - prev_prime)*1000/(current_time - last_report_time));
  prev_prime = p;
  last_report_time = current_time;
}

static void handle_signal(int signum)
{
  switch (signum)
  {
    case SIGINT:
      notify_event(received_sigint);
      break;
    case SIGTERM:
      notify_event(received_sigterm);
      break;
  }
}

static void init_signals(void)
{
  signal(SIGINT, handle_signal);
  signal(SIGTERM, handle_signal);
}

/* This function is called (via check_events()) from the top level sieve
   loops (prime_sieve() etc.). It can assume that it is safe to tighten any
   sieving parameters other than p_min and p_max.
*/
void process_events(uint64_t current_prime)
{
  /* event_happened was set last in notify_event(), so clear it first which
     ensures that if some signal arrives while we are in process_events()
     it might have to wait until the next sieve iteration to get processed,
     but it won't be lost.
  */
  event_happened = 0;

  if (clear_event(initialise_events))
  {
    init_signals();
    init_progress_report(current_prime);
  }

  if (clear_event(sieve_parameters_changed))
    init_progress_report(current_prime);

  if (clear_event(received_sigterm))
  {
    finish_srsieve("SIGTERM was received",current_prime);
    signal(SIGTERM,SIG_DFL);
    raise(SIGTERM);
  }

  if (clear_event(received_sigint))
  {
    finish_srsieve("SIGINT was received",current_prime);
    signal(SIGINT,SIG_DFL);
    raise(SIGINT);
  }

  if (clear_event(factor_found))
    next_report_due = sec_clock();

  if (clear_event(report_due))
    progress_report(current_prime);

  if (clear_event(save_due))
    write_checkpoint(current_prime);
}

void check_progress(void)
{
  uint32_t current_time = sec_clock();

  if (current_time >= next_save_due)
  {
    next_save_due += SAVE_PERIOD;
    notify_event(save_due);
  }

  if (current_time >= next_report_due)
  {
    next_report_due += REPORT_PERIOD;
    notify_event(report_due);
  }
}
