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

// all implicitly initialised to 0 ... mock Imp library for test
static int instream, outstream;
static FILE *input[4], *output[4];

// This was my first attempt to simulate %on %event/%signal in C
// and I was thinking it was looking pretty good ... until I realised
// that this is *dynamic* scoping, not static scoping.  I.e. if you
// trap an event at the head of a routine and that routine recurses,
// you end up with a *lot* of event traps in the stack.

// You know, I'm not sure in Imp whether an event handler in a recursive
// procedure would catch the signal at the most recent recursion or if it would
// unwind the stack all the way back to the first call on that procedure...
// (and what happens if the same event is signalled from within the on event
//  block - is there a parent handler still available or does it look for
//  one in syntactically-enclosing blocks?)

// Also I don't yet remove handlers when a procedure returns (though I
// guess code to do that could be inserted by the ImpToC translator,
// except I'm thinking now that with static scoping it's not something
// that ever needs to be done...)

typedef struct IMP_EVENT {
  int event;
  int subevent;
  int extra;
} IMP_EVENT;

typedef struct ONEVENT_HANDLER ONEVENT_HANDLER;
typedef struct ONEVENT_HANDLER {
  int eventmask;
  ONEVENT_HANDLER *parent_handler;
  jmp_buf env;
} ONEVENT_HANDLER;

static IMP_EVENT EVENT; // global
static ONEVENT_HANDLER *global_handler = NULL; // only declared and installed in an onevent block!

void signal_event(int event, int subevent, int extra) {
  ONEVENT_HANDLER *handler = global_handler;
  if (global_handler == NULL) {
    fprintf(stderr, "Unclaimed event %d,%d,%d\n", event, subevent, extra);
    // Monitor entered from IMP ... :-) - shenannigans with gdb???
    exit(1);
  }
  global_handler = global_handler->parent_handler; // unwind for subsequent calls
  EVENT.event = event;
  EVENT.subevent = subevent;
  EVENT.extra = extra;
  longjmp(handler->env, 1);
}

int caught_event(int event, int eventmask) {
  if ((eventmask&(1<<event)) != 0) return 1;
  fprintf(stderr, "Event %d was not trapped by this handler.  Passing the event back up the chain.\n", event);
  signal_event(EVENT.event, EVENT.subevent, EVENT.extra); // not claimed - pass it up the chain ...
}

int eventmask(int bitpos, ...) {
  va_list ap;
  int i;
  int mask = 0;
  va_start(ap, bitpos);
  for (i = bitpos; i >= 0; i = va_arg(ap, int)) mask |= 1<<i;
  va_end(ap);
  return (mask);
}

// Trying to look as much like Imp's %on %event n,n,n, %start as possible!
#define on_event(...) ( {                                                             \
      static ONEVENT_HANDLER this_handler;                                            \
      this_handler = (ONEVENT_HANDLER){.eventmask=eventmask(__VA_ARGS__),             \
                                       .parent_handler=global_handler} ; /* chain */  \
      global_handler = &this_handler;                                                 \
      handler_at_this_level = 1;                                                      \
      setjmp(this_handler.env) && caught_event(EVENT.event,this_handler.eventmask);   \
} )



void debug_traphandler_on_exit(void) {
  if (global_handler) {
    fprintf(stderr, "\nOn Event blocks still active as we exit() from this level:\n\n");
    while (global_handler) {
      fprintf(stderr, "jmp_buf at %p\n", global_handler->env);
      global_handler = global_handler->parent_handler;
    }
  } else {
    fprintf(stderr, "\nThere are *NO* On Event blocks still active as we exit() from this level");
  }
  fprintf(stderr, "\n");
}

#define ENTER() int handler_in_this_level = 0; do { } while(0)

void UNWIND(void) {
  if (global_handler && handler_in_this_level) {
    global_handler = global_handler->parent_handler;
  }
}

#define return(x) do {UNWIND(); return;} while(0)

// ===============================================================================

int main(int argc, char **argv) {

  auto void test_non_empty(char *file) {

    auto int open_and_read(int stream, char *filename) {
      int ch;

      // There are no onevent blocks in this procedure.  Just calls to signal_event...
      
      if ((input[stream] = fopen(filename, "r")) == NULL) {
        fprintf(stderr, "I failed to open the file.  I will %%signal 9\n");
        signal_event(9,stream,__LINE__);
      }
      ch = fgetc(input[stream]);
      if (ch == EOF) {
        fprintf(stderr, "File is empty.  I will %%signal 5\n");
        signal_event(5,stream,__LINE__);
      }
      instream = stream;
      fprintf(stderr, "I am in %s (%s) and have read a character successfully\n", __func__, __PRETTY_FUNCTION__);
      if (ch == 'X') return 'x';
      else if (ch == 'Y') return('y');
      // Must submit a feature request to gcc so that PRETTY_FUNCTION returns nested hierarchy!:
      //   e.g. main/test_non_empty/open_and_read
      // They're already doing something similar for c++, and nested procedures *are* a gcc
      //  extension after all, so nothing standard would break...
      fprintf(stderr, "Returning from 3rd level to 2nd level\n");
      return ch;
    }

    if (on_event(5)) {
      fprintf(stderr, "Event 5 caught.  Empty file %s\n", file);
      fprintf(stderr, "Exiting from second-level onevent block\n");
      debug_traphandler_on_exit();
      exit(EXIT_FAILURE);
    } 

    int ch = open_and_read(3, file);

    if (ch == 'x') then return;

    fprintf(stderr, "Input 3 opened - first character is %c\n", ch);
    fprintf(stderr, "Returning from 2nd level to top level\n");
  }

  char *testfile = "ZZZ";

  if (on_event(4, 9)) {
    fprintf(stderr, "Event 4 or 9 trapped correctly - Failed to open %s\n", testfile);
    fprintf(stderr, "Exiting from top-level onevent block\n");
    debug_traphandler_on_exit();
    exit(EXIT_FAILURE);
    // If we were to drop through from this onevent block, the event would no longer be trapped.
    // If you did want to repeat the code, the trick would be to place a label *above* this
    // onevent block, and goto it instead of dropping through.
  }

  test_non_empty(testfile);  // 3 tests: ZZZ does not exist, ZZZ is empty, ZZZ is non-empty

  fprintf(stderr, "Clean exit at end of top level\n");
  debug_traphandler_on_exit();
  exit(0);
}