// Signal handling part of i77 perms.

#define _POSIX_C_SOURCE 200809L

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <setjmp.h>
#include <unistd.h>
#include <string.h>
#include <stdarg.h>

#include "internalfile.h"
#include "psignal.h"

// Setting this up so that *all* IMP %signal calls map to linux signal SUGUSR1
// Any other Linux signals (such as generated by say divide-by-zero) will need
// to be trapped and converted into an IMP signal.  The previous implementation
// where imp signals were handled by longjmp but not linux signals has been
// removed. The replacement code is not yet complete.

// Somewhere in the transition from the test signal code to this library,
// the eventfm record had to be changed from a pointer to an actual record
// so this code is not finished and not tested.  Currently it is not called
// because the signal_event procedure doesn't invoke the C signal.

_imp_eventfm _imp_event;  // user visible EVENT record, also top of onevent trap stack.

// sig_atomic_t _imp_last_signal = 0;

/* Jump to the current innermost active event frame. */

void _imp_signal_handler(int sig) {
  // This is the code that is called when a Unix SIGNAL has been raised:
  
  //if (_imp_event == NULL) croak("NULL _imp_event\n");
  //_imp_last_signal = sig; // C signal type such as SIGUSR1

  if (sig == SIGUSR1) {
    // An Imp signal has already filled in a _imp_eventfm record with the extra information used by imp signals
    if (_imp_event.EVENT < 0 || _imp_event.EVENT > 15) {
      fflush(stdout); fprintf(stderr, "* RUNTIME ERROR: signal handler received an IMP signal %d - signals should be in the range 0 to 15.\n", _imp_event.EVENT);
      exit(EXIT_FAILURE);
    }

    // Note that although not yet done, %signal 0 is equivalent to %stop.
    // I'm considering making signal 0,0 a stop and signal 0,1 a %monitorstop
    // with the third parameter being the exit return code.
    
    if ((_imp_event.mask & (1<<_imp_event.EVENT)) != 0) {
      siglongjmp(_imp_event.env, 1);  // Go to the event block that handles this signal.
      fflush(stdout); fprintf(stderr, "* RUNTIME ERROR - LONGJUMP FAILED: %%signal %d, %d, %d, \"%s\"\n",
              _imp_event.EVENT, _imp_event.SUB, _imp_event.EXTRA, _imp_i2cstr(&_imp_event.MESSAGE));
      exit(EXIT_FAILURE);
    }
  
    // It was a valid signal type *but* this block does not catch it.  So remove this
    // signal handler from the chain and re-signal.  (Alternatively we could chain down
    // the signal handlers here, but re-signalling ought to be more consistent)
  } else {
    // This is a posix signal rather than an Imp one. Some signals we want to translate
    // into Imp signals (such as integer overflow, divide by zero etc) but others we
    // want to leave to the normal C mechanisms in most cases (such as timer events).
    // The details of which are converted to Imp signals may change as this compiler
    // is developed.
    fflush(stdout); fprintf(stderr, "* TEMPORARY PLACE-HOLDER: Posix signal %d detected.  Exiting.\n", _imp_event.EVENT);
    exit(EXIT_FAILURE); // We could also do an Imp backtrace at this point, but the
                        // better plan for unknown signals is to resignal them and
                        // eventually the top-level default handler just before main()
                        // should report or backtrace as appropriate.
  }
}

_imp_eventfm *_imp_on_event_enter(_imp_eventfm *f, ...) {
  // This is the conditional test that is executed at the head of an imp onevent block
  // in conjunction with sigsetjmp.  If sigsetjmp returns 0, it has set up the trap
  // for the signal; if it returns 1, it has caught the signal.
  
  // We have to remove the handler for this block if we re-signal within the block,
  // but we need to leave the handler in place if we drop through the %finish back into
  // this procedure.  Easiest way to do that is remove the handler on entry to the
  // handler block and restore it just as we exit it.  This will require code to be
  // generated by the compiler at the end of the onevent block.

  // *UNFORTUNATELY* the imp77 pass1 does not give us an easy way to detect the end of
  // an onevent block - the 'ON' ICODE emits a jump to the start of the block but that is
  // all.  This is going to be tricky :-(

  // OK, not so tricky: the %onevent statement is translated as:
  //    if (!_imp_on_event(3)) goto L_0002;
  // so what we need to do is insert the restore code immediately before "L_0002:" which
  // will correspond to just before the %finish of the "%on %event <n> %start ... %finish"
  // block.
  
  int mask = 0; /* (could easily extend to 32 bits) */
  va_list ap;
  va_start(ap, f);
  int count = 0;
  for (;;) {
    int i = va_arg(ap, int);
    if (i < 0) break;
    if (i <= 15) mask = mask | (1 << i);  /* Hopefully 1<<0 works correctly on this compiler, unlike gcc for 6809 :-( */
    count += 1;
    if (count > 16) {
      fflush(stdout); fprintf(stderr, "* INTERNAL ERROR: List of event numbers was not terminated by -1.\n");
      exit(EXIT_FAILURE);
    }
  }
  va_end(ap);
  
  f->prev = &_imp_event;
  _imp_event = *f;
  _imp_event.mask = mask;
  return f;

}

void _imp_on_event_leave(_imp_eventfm *f) {
  _imp_eventfm *lost = f; // The onevent block for this procedure.
                          // We need to remove it at every normal return from this procedure.
                          // We *might* be able to do this in the __cyg_ exit code, however
                          // doing it in the i2c code generator is much more efficient for
                          // the many procedures where events are not trapped.  To be done
                          // on every %result=, %return, and %end of a rt/fn/map/pred.

  // *** THIS MAY NOT BE GETTING CALLED IN THE CURRENT COMPILER VERSION ***
  
  _imp_event = *f->prev;
  free(lost);
}

// Currently being retooled for new c-signal based system:

void _signal_event(_imp_string file,
                   int line,
                   _imp_string message,
                   int event, int subevent, int extra) {
  _imp_event.EVENT = event;
  _imp_event.SUB = _imp_event.SUBEVENT = subevent;
  _imp_event.INFO = _imp_event.EXTRA = extra;
  _imp_event.MESSAGE = message;
  _imp_event.FILE = file;
  _imp_event.LINE = line;
  raise(SIGUSR1); // SIGUSR1 is "Imp %signal"...

  fflush(stdout); fprintf(stderr, "* ERROR: Called %%signal %d,%d,%d,\"%s\" but returned from the RAISE() call!??\n", event, subevent, extra, _imp_i2cstr(&message));
  exit(EXIT_FAILURE);
}
