/*************************************************************************
 *									 *
 *	 YAP Prolog 							 *
 *									 *
 *	Yap Prolog was developed at NCCUP - Universidade do Porto	 *
 *									 *
 * Copyright L.Damas, V. Santos Costa and Universidade do Porto 1985--	 *
 *									 *
 **************************************************************************
 *									 *
 * File:		signal.c						 *
 * comments:	Signal Handling & Debugger Support			 *
 *									 *
 *									 *
 *									 *
 *************************************************************************/
#ifdef SCCS
static char     SccsId[] = "%W% %G%";
#endif

#define HAS_CACHE_REGS 1

#include "Yap.h"
#include "Yatom.h"
#include "YapHeap.h"
#include "eval.h"
#include "yapio.h"
#ifdef TABLING
#include "tab.macros.h"
#endif /* TABLING */
#include <stdio.h>
#if HAVE_STRING_H
#include <string.h>
#endif
#if HAVE_MALLOC_H
#include <malloc.h>
#endif
#include <wchar.h>
#ifdef LOW_LEVEL_TRACER
#include <tracer.h>
#endif

#ifndef THREADS
#define worker_id 0
#endif

/*
 * The InteractSIGINT function is called after a normal interrupt had been caught.
 * It allows 6 possibilities: abort, continue, trace, debug, help, exit.
 */
static yap_signals
InteractSIGINT(int ch) {
#ifdef HAVE_SETBUF
  /* make sure we are not waiting for the end of line */
  YP_setbuf (stdin, NULL);
#endif
  switch (ch) {
    case 'a':
      /* abort computation */
      return YAP_ABORT_SIGNAL;
    case 'b':
      /* continue */
      return YAP_BREAK_SIGNAL;
    case 'c':
      /* continue */
      return YAP_NO_SIGNAL;
    case 'd':
      /* enter debug mode */
      return YAP_DEBUG_SIGNAL;
    case 'e':
      /* exit */
      Yap_exit(1);
      return YAP_EXIT_SIGNAL;
    case 'g':
      /* stack dump */
      return YAP_STACK_DUMP_SIGNAL;
    case 't':
      /* start tracing */
      return YAP_TRACE_SIGNAL;
#ifdef LOW_LEVEL_TRACER
    case 'T':
      toggle_low_level_trace();
      return YAP_NO_SIGNAL;
#endif
    case 's':
      /* show some statistics */
      return YAP_STATISTICS_SIGNAL;
    case EOF:
      return YAP_NO_SIGNAL;
    case 'h':
    case '?':
    default:
      /* show an helpful message */
      fprintf(stderr, "Please press one of:\n");
      fprintf(stderr, "  a for abort\n  c for continue\n  d for debug\n");
      fprintf(stderr, "  e for exit\n  g for stack dump\n  s for statistics\n  t for trace\n");
      fprintf(stderr, "  b for break\n");
      return YAP_NO_SIGNAL;
  }
}

/*
  This function talks to the user about a signal. We assume we are in
  the context of the main Prolog thread (trivial in Unix, but hard in WIN32)
 */
static yap_signals
ProcessSIGINT(void)
{
  CACHE_REGS
  int ch, out;
#if HAVE_ISATTY
  if (!isatty(0)) {
      return YAP_INT_SIGNAL;
  }
#endif
  LOCAL_PrologMode |= AsyncIntMode;
  do {
      ch = Yap_GetCharForSIGINT();
  } while (!(out = InteractSIGINT(ch)));
  LOCAL_PrologMode &= ~AsyncIntMode;
  return(out);
}

inline static void
do_signal(int wid, yap_signals sig USES_REGS)
{
#if THREADS
  __sync_fetch_and_or ( &REMOTE(wid)->Signals_, SIGNAL_TO_BIT(sig));
  if (!REMOTE_InterruptsDisabled(wid)) {
      REMOTE_ThreadHandle(wid).current_yaam_regs->CreepFlag_ =
	  Unsigned(REMOTE_ThreadHandle(wid).current_yaam_regs->LCL0_);
  }
#else
  LOCAL_Signals += SIGNAL_TO_BIT(sig);
  if (!LOCAL_InterruptsDisabled) {
      CreepFlag =
	  Unsigned(LCL0);
  }
#endif
}



inline static int
get_signal(yap_signals sig USES_REGS)
{
#if THREADS
  uint64_t old;

  // first, clear the Creep Flag, now if someone sets it it is their problem
  CalculateStackGap( PASS_REGS1 );
  // reset the flag
  if ( (old =__sync_fetch_and_and( &LOCAL_Signals, ~SIGNAL_TO_BIT(sig) ) ) !=
      SIGNAL_TO_BIT(sig)) {
      if (!LOCAL_InterruptsDisabled && LOCAL_Signals != 0) {
	  CreepFlag =  (CELL)LCL0;
      }
       if (!(old & SIGNAL_TO_BIT(sig)) ) {
	  // not there?
          return FALSE;
      }
     // more likely case, we have other interrupts.
      return TRUE;
  }
  // success, we are good
  return TRUE;
  // should we set the flag?
#else
  if (LOCAL_Signals & SIGNAL_TO_BIT(sig)) {
      LOCAL_Signals &= ~SIGNAL_TO_BIT(sig);
      if (!LOCAL_InterruptsDisabled && LOCAL_Signals != 0) {
	  CreepFlag =  (CELL)LCL0;
      } else {
	  CalculateStackGap( PASS_REGS1 );
      }
      return TRUE;
  } else {
      return FALSE;
  }
#endif
}

/**
  Function called to handle delayed interrupts. 
 */
int
Yap_HandleInterrupts( void )
{
  CACHE_REGS
  yap_signals sig;

  if ( get_signal( YAP_INT_SIGNAL PASS_REGS )) {
      if ( (sig = ProcessSIGINT()) != YAP_NO_SIGNAL )
	do_signal(worker_id, sig PASS_REGS);
      LOCAL_PrologMode &= ~InterruptMode;
      return 1;
  }
  return 0;
}

static Int 
p_creep( USES_REGS1 )
{
  Atom            at;
  PredEntry      *pred;

  at = AtomCreep;
  pred = RepPredProp(PredPropByFunc(Yap_MkFunctor(at, 1),0));
  CreepCode = pred;
  do_signal(worker_id, YAP_CREEP_SIGNAL PASS_REGS);
  return TRUE;
}

static Int 
p_creep_fail( USES_REGS1 )
{
  Atom            at;
  PredEntry      *pred;

  at = AtomCreep;
  pred = RepPredProp(PredPropByFunc(Yap_MkFunctor(at, 1),0));
  CreepCode = pred;
  do_signal(worker_id, YAP_CREEP_SIGNAL PASS_REGS);
  return FALSE;
}

static Int 
p_stop_creeping( USES_REGS1 )
{
  get_signal( YAP_CREEP_SIGNAL PASS_REGS );
  return TRUE;
}

static Int
p_creep_allowed( USES_REGS1 )
{
  if (PP != NULL) {
      get_signal(YAP_CREEP_SIGNAL PASS_REGS);
      return TRUE;
  }
  return FALSE;
}

void 
Yap_signal(yap_signals sig)
{
  CACHE_REGS
  do_signal(worker_id, sig PASS_REGS);
}

static Int
p_debug( USES_REGS1 );

void 
Yap_external_signal(int wid, yap_signals sig)
{
#if THREADS
  REGSTORE *regcache = REMOTE_ThreadHandle(wid).current_yaam_regs;
#endif
  do_signal(wid, sig PASS_REGS);
  LOCAL_PrologMode &= ~InterruptMode;
}

int
Yap_get_signal__(yap_signals sig USES_REGS)
{
  return get_signal(sig PASS_REGS);
}

// the caller holds the lock.
int 
Yap_has_signals__(yap_signals sig1, yap_signals sig2 USES_REGS)
{
  return LOCAL_Signals & (SIGNAL_TO_BIT(sig1)|SIGNAL_TO_BIT(sig2));
}


int 
Yap_only_has_signals__(yap_signals sig1, yap_signals sig2 USES_REGS)
{
  uint64_t  sigs = LOCAL_Signals;
  return sigs & (SIGNAL_TO_BIT(sig1) | SIGNAL_TO_BIT(sig2)) &&
      ! (sigs & ~(SIGNAL_TO_BIT(sig1) | SIGNAL_TO_BIT(sig2))) ;
}

#ifdef DEBUG

volatile int volat = 0;

static Int 
p_debug( USES_REGS1 )
{				/* $debug(+Flag) */
  int             i = IntOfTerm(Deref(ARG1));
  while (volat == 0) {
  }
  if (i >= 'a' && i <= 'z')
    GLOBAL_Option[i - 96] = !GLOBAL_Option[i - 96];
  return (1);
}
void Yap_loop(void);
void Yap_debug_end_loop(void);

void Yap_loop(void)
{
  while (volat == 0);
}

void Yap_debug_end_loop(void)
{
  volat = 1;
}
#endif

static Int
p_first_signal( USES_REGS1 )
{
  Atom at;
  yap_signals sig;

  while (TRUE) {
      uint64_t mask = LOCAL_Signals;
      if (mask == 0)
	return FALSE;
      sig = ffsll(mask);
     if (get_signal(sig PASS_REGS)) {
	  break;
      }
																											  }
  loop:
  switch (sig) {
    case YAP_INT_SIGNAL:
      sig = ProcessSIGINT();
      if (sig == YAP_INT_SIGNAL) {
	  at = AtomSigInt;
	  break;
      }
      if (sig != YAP_NO_SIGNAL)
	goto loop;
      return FALSE;
    case YAP_ABORT_SIGNAL:
      /* abort computation */
      LOCAL_PrologMode &= ~AsyncIntMode;
      if (LOCAL_PrologMode & (GCMode|ConsoleGetcMode|CritMode)) {
	  LOCAL_PrologMode |= AbortMode;
	  return -1;
      } else {
	  Yap_Error(PURE_ABORT, TermNil, "abort from console");
      }
      Yap_RestartYap( 1 );
      return FALSE;
    case YAP_CREEP_SIGNAL:
      at = AtomSigCreep;
      break;
    case YAP_TRACE_SIGNAL:
      at = AtomSigTrace;
      break;
    case YAP_DEBUG_SIGNAL:
      at = AtomSigDebug;
      break;
    case YAP_BREAK_SIGNAL:
      at = AtomSigBreak;
      break;
    case YAP_FAIL_SIGNAL:
      at = AtomFail;
      break;
    case YAP_STACK_DUMP_SIGNAL:
      at = AtomSigStackDump;
      break;
    case YAP_STATISTICS_SIGNAL:
      at = AtomSigStatistics;
      break;
#ifdef SIGALRM
    case YAP_ALARM_SIGNAL:
#endif
    case YAP_WINTIMER_SIGNAL:
      at = AtomSigAlarm;
      break;
#ifdef SIGVTALRM
    case YAP_VTALARM_SIGNAL:
      at = AtomSigVTAlarm;
      break;
#endif
    case YAP_EXIT_SIGNAL:
      Yap_exit(1);
      return FALSE;
    case YAP_WAKEUP_SIGNAL:
      at = AtomSigWakeUp;
      break;
    case YAP_ITI_SIGNAL:
      at = AtomSigIti;
      break;
#ifdef SIGPIPE
    case YAP_PIPE_SIGNAL:
      at = AtomSigPipe;
      break;
#endif
#ifdef SIGHUP
    case YAP_HUP_SIGNAL:
      at = AtomSigHup;
      break;
#endif
#ifdef SIGUSR1
    case YAP_USR1_SIGNAL:
      at = AtomSigUsr1;
      break;
#endif
#ifdef SIGUSR2
    case YAP_USR2_SIGNAL:
      at = AtomSigUsr2;
      break;
#endif
    default:
      return FALSE;
  }
  return Yap_unify(ARG1, MkAtomTerm(at));
}

static Int
p_continue_signals( USES_REGS1 )
{
  return p_first_signal( PASS_REGS1 );
}

void
Yap_InitSignalCPreds(void)
{
  /* Basic predicates for the debugger */
  Yap_InitCPred("$creep", 0, p_creep, SafePredFlag);
  Yap_InitCPred("$creep_fail", 0, p_creep_fail, SafePredFlag);
  Yap_InitCPred("$stop_creeping", 0, p_stop_creeping, SafePredFlag);
  Yap_InitCPred ("$first_signal", 1, p_first_signal, SafePredFlag|SyncPredFlag);
  Yap_InitCPred ("$continue_signals", 0, p_continue_signals, SafePredFlag|SyncPredFlag);
  Yap_InitCPred("$creep_allowed", 0, p_creep_allowed, 0);
#ifdef DEBUG
  Yap_InitCPred("sys_debug", 1, p_debug, SafePredFlag|SyncPredFlag);
#endif
}

void *Yap_InitSignals(int wid)
{
  void *ptr = (void *)malloc(sizeof(UInt)*REMOTE_MaxActiveSignals(wid));
  return ptr;
}