#define IN_PERMS_C 1
#define _GNU_SOURCE // for some gnu extensions in system header files.

#include "perms.h"

#include <limits.h> // PATH_MAX for valgrind code
#include <time.h> // strftime
int     _imp_InStream = 0,  _imp_OutStream = 0;
FILE    *_imp_INFILE,       *_imp_OUTFILE;

_imp_filedata _imp_infile[256];
_imp_filedata _imp_outfile[256];

_imp_string   _imp_promptstr;

// similar to C's __LINE__ and __FILE__ for diagnostics:
int _imp_current_line;
char *_imp_current_file;

static int opt_info = 0;

//const double PI = 3.141592653589793238462;
const _imp_string1 SNL = {.charno = {1, '\n'} };
const _imp_string1 _imp_emptystring = { .length = 0 };
static char *_imp_Progname = "<imp main program>"; // get from argv[0]
  
eventfm _imp_Event; // global
_imp_on_event_handler *global_handler;

static void _imp_init_files(void) {
  int i;

  // inpos, outpos, lastchar and nextchar will require a lot of work
  // to implement properly.  Current code is much of a placeholder.
  
  for (i = 0; i < 256; i++) {
    _imp_infile[i].f = NULL;
    _imp_infile[i].fname = _imp_str_literal("<null>");
    _imp_infile[i].streamno = i;
    _imp_infile[i].lastchar = '\n';
    _imp_infile[i].nextchar = -1;
    _imp_infile[i].inpos = 0;
    _imp_outfile[i].f = NULL;
    _imp_outfile[i].fname = _imp_str_literal("<null>");
    _imp_outfile[i].streamno = i;
    _imp_outfile[i].lastchar = '\n';
    _imp_outfile[i].nextchar = -1;
    _imp_outfile[i].outpos = 0;
  }
  _imp_infile[0].f = fopen("/dev/tty", "r");
  _imp_infile[0].fname = _imp_str_literal("<console>");

  _imp_outfile[0].f = fopen("/dev/tty", "w"); // or could use stderr
  _imp_outfile[0].fname = _imp_str_literal("<console>");

  _imp_infile[0].inpos = 0;

  _imp_infile[1].f = stdin;
  _imp_infile[1].fname = _imp_str_literal("<stdin>");

  _imp_outfile[1].f = stdout;
  _imp_outfile[1].fname = _imp_str_literal("<stdout>");

  _imp_outfile[1].outpos = 0;

  // Default currently set to stdin and stdout.
  // This is not the same as selctinput(0) and selectoutput(0) which is /dev/tty
  // which are needed for interactive prompting.  Do an explicit selectinput(0)
  // in your code if it is meant to be an interactive program.
  _imp_InStream = 1; _imp_OutStream = 1;
  _imp_INFILE  = _imp_infile[_imp_InStream].f;
  _imp_OUTFILE = _imp_outfile[_imp_OutStream].f;

  _imp_promptstr = _imp_str_literal("Data: ");
}


// A system() analog that behaves like sprintf, to make it easier
// to access the output of a system command from C.
// This is an initial simple version, but ideally it should be
// expanded to allow stdargs formatting of the command string.
int sysnprintf(char *out, int maxlen, char *command) {
  FILE *pipe;
  pipe = popen(command, "r");
  for (int i = 0; i < maxlen-1; i++) {
    int c = fgetc(pipe);
    if (c == EOF) { out[i] = '\0'; pclose(pipe); return i; }
    out[i] = c;
  }
  return 0;
}


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>

static int running_under_debugger(void) {
  int fd = open("/proc/self/status", O_RDONLY);
  if (fd == -1) return 0;

  char buf[4096];
  ssize_t n = read(fd, buf, sizeof(buf) - 1);
  close(fd);
  if (n <= 0) return 0;

  buf[n] = '\0';

  const char *tag = "TracerPid:";
  char *p = strstr(buf, tag);
  if (!p) return 0;

  p += strlen(tag);
  while (*p && isspace((unsigned char)*p)) p++;
  /* Non-zero TracerPid => some debugger (gdb, lldb, etc.) */
  return (*p && *p != '0');
}


#ifdef VALGRIND_AVAILABLE
#include <valgrind/valgrind.h>   /* for RUNNING_ON_VALGRIND */

/*
 * Put this at the very start of main(), before you do anything else.
 *
 * int main(int argc, char **argv) {
 *     restart_under_valgrind_if_needed(argc, argv);
 *     ...
 * }
 */

void restart_under_valgrind_if_needed(int argc, char **argv)
{
    // This rather hacky code causes Imp programs to run automatically under Valgrind (unless you ask for --no-valgrind)
    // but sometimes you want to run explicitly under gdb instead, so for those cases we will check to see if a debugger
    // is attached and if so, will avoid valgrind altogether.
  
    if (running_under_debugger()) return;
    
#ifdef PARM_OPT
    return;  // Only invoke valgrind if checks are requested, i.e. -O was not given.
#endif
  
    // even if compiled for valgrind support, don't invoke it if the first command-line option was --nv:
    
    if ((argc > 1) && ((strcmp(argv[1], "--nv")==0) || (strcmp(argv[1], "-nv")==0) || (strcmp(argv[1], "--no-valgrind")==0))) {
      for (int i = 1; i <= argc; i++) {
        argv[i] = argv[i+1]; // will include terminal NULL of argv[argc]
      }
      return;
    }
    
    if ((argc > 1) && (strcmp(argv[1], "--info")==0)) {
      for (int i = 1; i <= argc; i++) {
        argv[i] = argv[i+1]; // will include terminal NULL of argv[argc]
      }
      argc -= 1;
      opt_info = 1;
    }
    
    if ((RUNNING_ON_VALGRIND) || getenv("RUN_UNDER_VALGRIND_DONE")) {
      /* already under Valgrind? Let's not get into an infinite loop of valgrinds! */
      return;
    }
    setenv("RUN_UNDER_VALGRIND_DONE", "1", 1);

    /* Build new argv: [valgrind, options..., original_argv[0..]] */

    // TO DO:  As well as checking that valgrind is available, we need to
    // check that the version is >= valgrind-3.14 which is the first version
    // that supports the --exit-on-first-error= option, necessary to make the
    // output look like what Imp users expect.

    const char *vg;
    char version[1024] = { 0 };
    // hunt around a little to find a suitable version.  Written mainly for my home system
    // but might work for others too...
    sysnprintf(version, 1024, "valgrind --version");
    if ((strncmp(version, "valgrind", strlen("valgrind")) != 0) || (strcmp(version, "valgrind-3.13") > 0)) {
      version[0] = '\0';
      sysnprintf(version, 1024, "/snap/bin/valgrind --version");
      if (strncmp(version, "valgrind", strlen("valgrind")) == 0) {
        vg = "/snap/bin/valgrind";
      } else {
        fprintf(stderr, "? Warning: valgrind not available.  Running without checks.\n");
        return;
      }
    } else vg = "valgrind";
    
    /* Some options that help with the reporting */
    const char *vg_args_fixed[] = {
        "--track-origins=yes",
        "--leak-check=full",
        // "--show-leak-kinds=all", // changed to tidy up some of the valgrind messages...
        "--show-leak-kinds=none",
        "--exit-on-first-error=yes",
        "-q",
          "--error-exitcode=1", // this one is kind of critical to be available...
        NULL
    };

    /* Count fixed options. */
    int vg_fixed_count = 0; while (vg_args_fixed[vg_fixed_count] != NULL) vg_fixed_count++;

    /* New argv size: valgrind + fixed + original argc + NULL */
    int new_argc = 1 + vg_fixed_count + argc;
    char **new_argv = calloc((size_t)new_argc + 1, sizeof(char *));
    if (!new_argv) {
        perror("calloc");
        exit(1);
    }

    int idx = 0;
    new_argv[idx++] = (char *)vg;            /* argv[0] = valgrind path */

    if (opt_info) fprintf(stderr, "! Executing %s ", (char *)vg);
    for (int i = 0; i < vg_fixed_count; i++) {
        new_argv[idx] = (char *)vg_args_fixed[i];
        if (opt_info) fprintf(stderr, " %s", new_argv[idx]);
        idx += 1;
    }

    /* Append original argv[0..argc-1] */
    for (int i = 0; i < argc; i++) {
        new_argv[idx] = argv[i];
        if (opt_info) fprintf(stderr, " %s", new_argv[idx]);
        idx += 1;
    }

    new_argv[idx] = NULL;
    if (opt_info) fprintf(stderr, "\n");

    /* Replace current process image with valgrind + our program. */
    // Was it an absolute or relative invocation?
    if (*vg == '/' || *vg == '.') {
      // absolute
      execv(vg, new_argv);
    } else {
      // so use the $PATH
      execvp(vg, new_argv);
    }

    /* If execv returns, it's an error. */
    fprintf(stderr, "%s: %s - %s\n", _imp_Progname, strerror(errno), vg);
    //perror("execv valgrind");
    exit(1);
}

#else

void restart_under_valgrind_if_needed(int argc, char **argv) {
  // apparently we've been told that valgrind was not available.
  // Maybe use gdb for similar effect if it's available?
  // Meanwhile a belt&braces check...
  char vg[512] = {'\0'};
  int rc = sysnprintf(vg, sizeof(vg), "which valgrind");
  if ((rc == 0) && (strlen(vg) > 3)) {
    fprintf(stderr, "! NOTICE: valgrind may be available on this system.  Try adding -DVALGRIND_AVAILABLE to the compilation of this file.\n");
  }
}
static const int RUNNING_ON_VALGRIND = 0;

#endif

#include <execinfo.h>  // backtrace, backtrace_symbols_fd
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <limits.h>

void _imp_generic_backtrace(void) {
  if (!RUNNING_ON_VALGRIND) {
    fprintf(stderr, "BACKTRACE NOT AVAILABLE\n\n");
    exit(1);
  }

  // Get exe path
  char exe_path[PATH_MAX];
  ssize_t len = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1);
  if (len == -1) {
    fprintf(stderr, "backtrace: readlink failed\n\n");
    exit(1);
  }
  exe_path[len] = '\0';

  // Capture backtrace
  void *trace[32];
  int n_ips = backtrace(trace, 32);
  if (n_ips <= 1) {  // Skip this frame
    fprintf(stderr, "backtrace failed\n\n");
    exit(1);
  }

  // Declare linker symbols (GNU ld provides these)
  extern char __executable_start[], etext[];
  
  for (int i = 1; i < n_ips; i++) {  // Skip IMP_SIGNAL frame
    
    uintptr_t ip = (uintptr_t)trace[i];
    if (ip < (uintptr_t)__executable_start || ip > (uintptr_t)etext) continue;
    
    //uintptr_t ip = (uintptr_t)trace[i];
    //if (ip < 0x8048000 || ip > 0x8050000) continue;  // App range (adjust)
    
    char cmd[PATH_MAX+512];
    snprintf(cmd, sizeof(cmd), "addr2line -e %s -iC -f -p %p", exe_path, trace[i]);

    FILE *pipe = popen(cmd, "r");
    if (pipe) {
      char linebuf[512];
      if (fgets(linebuf, sizeof(linebuf), pipe)) {
        linebuf[strcspn(linebuf, "\n")] = 0;
        if (strncmp(linebuf, "_signal_event ", strlen("_signal_event ")) == 0) {
        } else if (strncmp(linebuf, "_start ", strlen("_start ")) == 0) {
        } else if (strncmp(linebuf, "_imp_", strlen("_imp_")) == 0) {
        } else {
          fprintf(stderr, "    Entered from %s\n", linebuf);
        }
      }
      pclose(pipe);
    }
  }
}

void _imp_fallback_signal_handler(int eventno, int subevent, int extra, char *message) {
  fprintf(stderr, "\n* Monitor entered from IMP - %%signal %d,%d,%d  %s\n", eventno, subevent, extra, message);
  _imp_generic_backtrace();
  exit(1);
}

#ifdef FALLBACK_TRACE_MECHANISM
// trace entry and exit from procedures as a debugging aid.  Not used much.
// Backtrace could use this info too if saved on a stack but better to use
// proper system backtrace if available.  Still to do: add a command-line
// option to invoke runtime tracing.  There is code in the old imptoc-perms.c
// file for runtime tracing and a poor man's backtrace based on this mechanism
// but the current i2c does not insert these calls anywhere.  The fallback
// code for backtracing in _imp_trace_backtrace has not been implemented in
// i2c yet, but _imp_generic_backtrace which depends on the code having been
// restarted under valgrind is available and is currently the mechanism of
// choice for backtracing, although valgrind is not guaranteed to be available,
// either because it is not installed on the user's system or because the imp
// file has been compiled with optimisations enabled which preclude runtime
// checks and running under valgrind.  

// There are actually multiple backtrace options potentially possible; another
// is to reinvoke the executable under gdb rather than valgrind, and have a
// gdb script be executed on a failure, invoking gdb's "bt" command, such as by:
// "/usr/bin/gdb", "-q", "-nx", "-nh", "-return-child-result", "-ex", "run",
// "-ex", "bt full", "--args" followed by the program's initial arguments.


// (However only way to detect if a running program was compiled with -g (which
//  would enable a proper backtrace is to actually examine itself to see if it
//  contains a symbol table with debugging, such by:
//          objdump --syms $argv[0] | fgrep .debug_info && echo YES
//  Unless we have a symbol table there's not much point in invoking via gdb -
//  better to fall back to explicit entry/exit tracing if present, or implicit
//  cyg_ version if present, and then back to gdb with no symbol tables only if
// those are not available.

// There's *also* a 'libbacktrace' at ~/src/imp77/hacks/libbacktrace on my home
// linux - I don't think it will be needed but I should do some experiments to see
// what it supports.  gdb is the only option I'm aware of that prints out the local
// variables in a backtrace.

// Another option is the cygwin trace/profile code '__cyg_profile_func_enter' etc
// ( https://codingrelic.geekhold.com/2010/09/gcc-function-instrumentation.html ),
// then there is the sneaky code in https://justine.lol/ftrace/ which relies on
// rewriting the executable at runtime to patch some pre-inserted NOP instructions
// with tracing code! (But only if you're using the Cosmopolitan C Compiler suite
// for multi-system fat binaries on 64 bit hosts)

// There are also other profiling support options potentially available which I
// haven't even considered yet.  I would just like to point out that the early Imp
// compilers on EMAS were quite well integrated with debuggers and profiler support
// (eg vagrens), so none of these options are out of character with an original
// implementation!

static int _imp_trace_depth = 0;
int _imp_trace_enter(int line, char *file, char *funcname) {
  for (int i = _imp_trace_depth; i > 0; i--) fputc(' ', stderr);
  fprintf(stderr, "\"%s\", %d: > %s\n", file, line, funcname);
  _imp_trace_depth += 1;
  return 0; // I've forgotten how the return value of these is used
}

int _imp_trace_exit(int line, char *file, char *funcname) {
  _imp_trace_depth -= 1;
  for (int i = _imp_trace_depth; i > 0; i--) fputc(' ', stderr);
  fprintf(stderr, "\"%s\", %d: < %s\n", file, line, funcname);
  return 0; // I've forgotten how the return value of these is used
}
#endif

void _imp_monitor(int n, int line, char *file, const char *funcname) {
  if (n <= 0) {
    char *fun;
    if (file == NULL) file = "<unknown file>";
    if (funcname == NULL) fun = "<unknown function>"; else fun = (char *)funcname;
    fprintf(stderr, "\n* %%MONITOR entered from IMP in %s at %s:%d\n", *funcname == '\0' ? "block" : fun, file, line);
  } else {
    fprintf(stderr, "\n* %%MONITOR %d entered from IMP in %s at %s:%d\n", n, *funcname == '\0' ? "block" : funcname, file, line);
  }
  _imp_generic_backtrace(); // sometimes fails, I really should add a fallback method...
}

// imptoc translated the begin/endofprogram block into _imp_mainep and
// called _imp_mainep from a main in perms.c.  This time around we're letting
// the main program be C's main() and we're initialising the imp world via a
// call to _imp_initialise below.  It's not clear if either way is better.

// as an extension to Imp77 on linux, I'm supplying a perm call to argc and argv
// which will eventually pick up the arguments passed here.  Note that these will
// be functions, not arrays or maps, so cannot be written to.  This is deliberate.

static int _imp_global_argc = 0;
static char **_imp_global_argv = NULL;

int _imp_ARGC(void) {
  return _imp_global_argc;
}

_imp_string255 _imp_ARGV(int n) {
  // Add test that we've initialised, and range checking, here
  if (n > _imp_global_argc) {
    fprintf(stderr, "* MONITOR ENTERED FROM IMP - ARRAY BOUND ERROR: argv[%d] (argc = %d)\n", n, _imp_global_argc);
    // or signal.  (later)
    _imp_generic_backtrace();
    exit(1);
  }
  if (n == _imp_global_argc) {
    return _imp_str_literal("");
  } else return _imp_c2istr(_imp_global_argv[n]);
}

_imp_string *_imp_strcat(_imp_string *left, _imp_string right) {
  /*TO DO*/
  int llen = *_imp_LENGTH(left);
  int rlen = *_imp_LENGTH(&right);
  int moved = 0;
  for (;;) {
    if (moved == rlen) break;
    moved += 1;
    left->charno[llen+moved] = right.charno[moved];
    *_imp_LENGTH(left) += 1;
  }
  return left;
}

void _imp_initialise(int argc, char **argv) {
  _imp_Progname = argv[0];
  _imp_global_argc = argc;
  _imp_global_argv = argv;
  restart_under_valgrind_if_needed(argc, argv);
  _imp_init_files(); // Initialise Imp I/O streams.
}

void _imp_issue_prompt(void) {

  // Unfortunately for prompts to work properly, every I/O operation has to
  // keep track of 'lastchar' (and incidentally, also 'outpos')
  // So doing that at the imp library level is not idea, but it'll
  // have to do for now.

  // Note prompting does not work if selected streams are both stream 1,
  // even if that does by coincidence happen to be the console.  At least for now.
  // If I do check the interactivity of stdin and stdout, I should only do it
  // at the point of defining the stream, with a lightweight check at the
  // point of selecting the stream, and a trivial check of that result at
  // the point of outputting a prompt!
  
  if (_imp_OutStream == 0
      && _imp_outfile[0].lastchar == '\n'
      && _imp_InStream == 0
      && _imp_infile[0].lastchar == '\n') {
    
    _imp_PRINTSTRING(_imp_promptstr); fflush(_imp_OUTFILE);
    
    if (*_imp_LENGTH(&_imp_promptstr) > 0) {
      _imp_outfile[_imp_OutStream].lastchar
        = *_imp_CHARNO(&_imp_promptstr, *_imp_LENGTH(&_imp_promptstr)-1);
    }
  }
}

// external not static inline in perms.h
int _imp_resolve(_imp_string s, _imp_string *left, _imp_string match, _imp_string *right) {
  char *matches;
  int matchstart;
  matches = (char *)memmem(s.cstr.s, s.length, match.cstr.s, match.length);
  if (matches) {
    matchstart = matches-s.cstr.s;
    if (left) {
      strncpy(left->cstr.s, s.cstr.s, matchstart);
      //left->cstr.s[matchstart] = '\0';
      left->length = matchstart;
    }
    if (right) {
      strncpy(right->cstr.s,
              s.cstr.s+matchstart+strlen(match.cstr.s),
              strlen(s.cstr.s+matchstart+strlen(match.cstr.s))+1);//<--needs length fix
      right->length = strlen(right->cstr.s);//<--needs length fix
    }
    return 1/*True*/;
  } else {
    // Did not contain match string.  Do not update left or right.
    return 0/*False*/;
  }
}

// this one is implemented as a static inline in perms.h

//_imp_string _imp_join(_imp_string left, _imp_string right) {
//  fprintf(stderr, "* _imp_join not yet implemented.\n");
//  exit(1);
//}

int _imp_strcmp(_imp_string left, _imp_string right) {
  int comp, i = 1;
  for (;;) {
    comp = right.charno[i] - left.charno[i];
    if (comp != 0) return comp;
    if (i == left.length && i == right.length) return comp;
    if (i == left.length) return -1;
    if (i == right.length) return 1;
    i += 1;
    if (i == 256) return 1;//ERROR!
  }
}

eventfm _imp_Event; // global

_imp_on_event_handler *global_handler; // only declared and installed in an onevent block!

void _signal_event(char *file, int line, _imp_string info, int event, int subevent, int extra) {
  _imp_fallback_signal_handler(event, subevent,  extra, "");
}

int _imp_caught_on_event(int event, int bitpos, ...) {
  // resignals if not in catch-list.
  return 0; // *TO DO*
}

//int           eventmask(int bitpos, ...);

int _imp_caught_fault(int event, int bitpos, ...) {
  // does not signal. just returns true/false
  return 0; // *TO DO*
}

//void _imp_fault(const char *format, ...) {
//  // calls generated by the compiler. Not for users.
//}

//void _imp_warn(const char *format, ...) {
//}

void _imp_readbyte(unsigned char *dest) {
  int num;
  _imp_issue_prompt();
  int rc = fscanf(_imp_INFILE, "%d", &num);
  // if (rc != 1) _imp_signal(?,?,?);
  // first do a range check for 0:255 (or possibly -128:127 or even -128:255)
  *dest = num;
  (void)rc;
}

void _imp_readshort(short int *dest) {
  int num;
  _imp_issue_prompt();
  int rc = fscanf(_imp_INFILE, "%hd", dest);
  // if (rc != 1) _imp_signal(?,?,?);
  // first do a range check for 0x8000 to 0x7FFF
  (void)rc;
}

void _imp_readint(int *dest) {
  int num;
  _imp_issue_prompt();
  int rc = fscanf(_imp_INFILE, "%d", dest);
  // if (rc != 1) _imp_signal(?,?,?);
  // first do a range check for 0x80000000 to 0x7FFFFFFF
  (void)rc;
}

void _imp_readlong(long long int *dest) {
  _imp_issue_prompt();
  int rc = fscanf(_imp_INFILE, "%lld", dest);
  // if (rc != 1) _imp_signal(?,?,?);
  (void)rc;
}

void _imp_readfloat(float *dest) {
  _imp_issue_prompt();
  int rc = fscanf(_imp_INFILE, "%f", dest);
  // if (rc != 1) _imp_signal(?,?,?);
  // possible warning for loss of precision?
  (void)rc;
}

void _imp_readdouble(double *dest) {
  _imp_issue_prompt();
  int rc = fscanf(_imp_INFILE, "%lf", dest);
  // if (rc != 1) _imp_signal(?,?,?);
  (void)rc;
}

void _imp_readsymbol(int *P) { _imp_issue_prompt(); *P = fgetc(_imp_INFILE); }
void _imp_readch(int *P) { _imp_issue_prompt(); *P = fgetc(_imp_INFILE); }
int _imp_nextsymbol(void) { _imp_issue_prompt(); int c = fgetc(_imp_INFILE); ungetc(c, _imp_INFILE); return c; }
int _imp_nextch(void) { _imp_issue_prompt(); int c = fgetc(_imp_INFILE); ungetc(c, _imp_INFILE); return c; }
void _imp_skipsymbol(void) { _imp_issue_prompt(); (void) fgetc(_imp_INFILE); }
void _imp_printsymbol(char SYM) { if (_imp_OUTFILE == NULL) { fprintf(stderr, "* OUTFILE is NULL\n"); exit(1); } fputc(SYM, _imp_OUTFILE); if (_imp_OutStream == 0) fflush(_imp_OUTFILE); }
void _imp_printch(char SYM) { if (_imp_OUTFILE == NULL) { fprintf(stderr, "* OUTFILE is NULL\n"); exit(1); } fputc(SYM, _imp_OUTFILE); if (_imp_OutStream == 0) fflush(_imp_OUTFILE); }
void _imp_printstring(_imp_string S) {
  // fprintf(_imp_OUTFILE, "%*s", S.length, S.cstr.s);
  if (_imp_OUTFILE == NULL) { fprintf(stderr, "* OUTFILE is NULL\n"); exit(1); }
  for (int i = 1; i <= S.length; i++) fputc(S.charno[i], _imp_OUTFILE);
  if (_imp_OutStream == 0) fflush(_imp_OUTFILE);
}
void _imp_write(int V, int P) { if (_imp_OUTFILE == NULL) { fprintf(stderr, "* OUTFILE is NULL\n"); exit(1); } fprintf(_imp_OUTFILE, P < 0 ? "%*d" : " %*d", P, V); if (_imp_OutStream == 0) fflush(_imp_OUTFILE); } // or swap.          // IMPORTANT: See https://gtoal.com/imp77/reference-manual/IO-LIBRARY-EXPERIMENTS.{imp,txt}
void _imp_readitem(_imp_string *S) { _imp_issue_prompt(); fprintf(stderr, "* READITEM not implemented\n"); exit(1); }
void _imp_readstring(_imp_string *S) { _imp_issue_prompt(); fprintf(stderr, "* READSTRING not implemented\n"); exit(1); }
void _imp_readtext(_imp_string *S, int DELIM) { _imp_issue_prompt(); fprintf(stderr, "* READTEXT not implemented\n"); exit(1); }
_imp_string _imp_nextitem(void) { _imp_issue_prompt(); return _imp_TOSTRING(_imp_NEXTSYMBOL()); }
void _imp_readline(_imp_string *S) { _imp_issue_prompt(); fprintf(stderr, "* READLINE not implemented\n"); exit(1); }
int _imp_instream(void) { return _imp_InStream; }
int _imp_outstream(void) { return _imp_OutStream; }
int _imp_inputstream(void) { return _imp_InStream; }
int _imp_outputstream(void) { return _imp_OutStream; }
_imp_string _imp_inputname(void) { return _imp_infile[_imp_InStream].fname; }
_imp_string _imp_outputname(void) { return _imp_outfile[_imp_OutStream].fname; }
_imp_string _imp_infilename(void) { return _imp_infile[_imp_InStream].fname; }
_imp_string _imp_outfilename(void) { return _imp_outfile[_imp_OutStream].fname; }
void _imp_selectinput(int N) { _imp_InStream = N; _imp_INFILE  = _imp_infile[_imp_InStream].f; }
void _imp_selectoutput(int N) { _imp_OutStream = N; _imp_OUTFILE = _imp_outfile[_imp_OutStream].f; }

void _imp_openinput(int N, _imp_string FD) {
  FILE *f;
  // Should we warn if there is already a stream open on this channel? (probably)
  f = fopen(_imp_i2cstr(&FD), "r");
  if (f == NULL) {
    //_imp_signal(9,0,N,strerror(errno)); // if signals not enabled, drop through
  }
  _imp_infile[N].f = f;
  _imp_infile[N].fname = FD;
  _imp_infile[N].streamno = N;
  _imp_infile[N].lastchar = '\n';
  _imp_infile[N].nextchar = -1;
  _imp_infile[N].inpos = 0;
}

void _imp_openbinaryinput(int N, _imp_string FD) {
  FILE *f;
  f = fopen(_imp_i2cstr(&FD), "rb");
  if (f == NULL) {
    //_imp_signal(9,0,N,strerror(errno)); // if signals not enabled, drop through
  }
  _imp_infile[N].f = f;
  _imp_infile[N].fname = FD;
  _imp_infile[N].streamno = N;
  _imp_infile[N].lastchar = '\n';
  _imp_infile[N].nextchar = -1;
  _imp_infile[N].inpos = 0;
}

void _imp_openoutput(int N, _imp_string FD) {
  FILE *f;
  f = fopen(_imp_i2cstr(&FD), "w");
  if (f == NULL) {
    //_imp_signal(9,0,N,strerror(errno)); // if signals not enabled, drop through
  }
  _imp_outfile[N].f = f;
  _imp_outfile[N].fname = FD;
  _imp_outfile[N].streamno = N;
  _imp_outfile[N].lastchar = '\n';
  _imp_outfile[N].outpos = 0;
}

void _imp_openbinaryoutput(int N, _imp_string FD) {
  FILE *f;
  f = fopen(_imp_i2cstr(&FD), "wb");
  if (f == NULL) {
    //_imp_signal(9,0,N,strerror(errno)); // if signals not enabled, drop through
  }
  _imp_outfile[N].f = f;
  _imp_outfile[N].fname = FD;
  _imp_outfile[N].streamno = N;
  _imp_outfile[N].lastchar = '\n';
  _imp_outfile[N].outpos = 0;
}

void _imp_defineinput(int I, _imp_string SPEC) { fprintf(stderr, "* DEFINEINPUT not implemented\n"); exit(1); }
void _imp_defineoutput(int I, _imp_string SPEC) { fprintf(stderr, "* DEFINEOUTPUT not implemented\n"); exit(1); }

void _imp_closeinput(void) {
  int rc;
  // TO DO: check validity of _imp_InStream
  rc = fclose(_imp_infile[_imp_InStream].f); // aka INFILE
  _imp_infile[_imp_InStream].f = _imp_INFILE = NULL;
  if (rc != 0) _imp_signal(9, 0, _imp_InStream, strerror(errno));
  // if signals not enabled, drop through
  _imp_InStream = -1;
}

void _imp_closeoutput(void) {
  int rc;
  // TO DO: check validity of _imp_OutStream
  rc = fclose(_imp_outfile[_imp_OutStream].f); // aka OUTFILE
  _imp_outfile[_imp_OutStream].f = _imp_OUTFILE = NULL;
  if (rc != 0) _imp_signal(9, 1, _imp_OutStream, strerror(errno));
  // if signals not enabled, drop through...
  _imp_OutStream = -1;
  // or should we default to stream 0 on closing any other stream?
  // While disallowing closing of stream 0?
  // also do OUTFILE = NULL; or OUTFILE = stderr; ?
}

void _imp_abandoninput(void) { fprintf(stderr, "* ABANDONINPUT not implemented\n"); exit(1); } // ABANDON INPUT and OUTPUT not yet added to perms.h
void _imp_abandonoutput(void) { fprintf(stderr, "* ABANDONOUTPUT not implemented\n"); exit(1); }

void _imp_resetinput(void) {
  fseek(_imp_infile[_imp_InStream].f, 0L, SEEK_SET);
}
// *Is* there a RESET OUTPUT?

void _imp_inputposition(void) {    // ftell
  fprintf(stderr, "*** ERROR: Empty version of %s called!\n", __PRETTY_FUNCTION__);
}

void _imp_outputposition(void) {
  fprintf(stderr, "*** ERROR: Empty version of %s called!\n", __PRETTY_FUNCTION__);
}

void _imp_setinput(int P) {
  fseek (_imp_INFILE, P, SEEK_SET);
}
void _imp_setoutput(int P) {
  fseek (_imp_OUTFILE, P, SEEK_SET);
}

void _imp_positioninput(int P) {     // fseek
  _imp_setinput(P);
}
void _imp_positionoutput(int P) {
  _imp_setoutput(P);
}


void _imp_space(void) { if (_imp_OUTFILE == NULL) { fprintf(stderr, "* OUTFILE is NULL\n"); exit(1); } fprintf(_imp_OUTFILE, " "); if (_imp_OutStream == 0) fflush(_imp_OUTFILE); }
void _imp_spaces(int N) { if (_imp_OUTFILE == NULL) { fprintf(stderr, "* OUTFILE is NULL\n"); exit(1); } while (N --> 0) fprintf(_imp_OUTFILE, " "); if (_imp_OutStream == 0) fflush(_imp_OUTFILE); }
void _imp_newpage(void) { if (_imp_OUTFILE == NULL) { fprintf(stderr, "* OUTFILE is NULL\n"); exit(1); } fprintf(_imp_OUTFILE, "\f"); if (_imp_OutStream == 0) fflush(_imp_OUTFILE); }
void _imp_newline(void) { if (_imp_OUTFILE == NULL) { fprintf(stderr, "* OUTFILE is NULL\n"); exit(1); } fprintf(_imp_OUTFILE, "\n"); if (_imp_OutStream == 0) fflush(_imp_OUTFILE); }
void _imp_newlines(int N) { if (_imp_OUTFILE == NULL) { fprintf(stderr, "* OUTFILE is NULL\n"); exit(1); } while (N --> 0) fprintf(_imp_OUTFILE, "\n"); if (_imp_OutStream == 0) fflush(_imp_OUTFILE); }
void _imp_print(double R, int BEFORE, int AFTER) { if (_imp_OUTFILE == NULL) { fprintf(stderr, "* OUTFILE is NULL\n"); exit(1); } fprintf(_imp_OUTFILE, "%*f", AFTER, R); if (_imp_OutStream == 0) fflush(_imp_OUTFILE); }         // IMPORTANT: See https://gtoal.com/imp77/reference-manual/IO-LIBRARY-EXPERIMENTS.{imp,txt}
void _imp_printfloating(double R, int BEFORE, int AFTER) { if (_imp_OUTFILE == NULL) { fprintf(stderr, "* OUTFILE is NULL\n"); exit(1); } fprintf(_imp_OUTFILE, "%*f", AFTER, R); if (_imp_OutStream == 0) fflush(_imp_OUTFILE); } // TO DO          // IMPORTANT: See https://gtoal.com/imp77/reference-manual/IO-LIBRARY-EXPERIMENTS.{imp,txt}
void _imp_printfl(double R, int PLACES) { if (_imp_OUTFILE == NULL) { fprintf(stderr, "* OUTFILE is NULL\n"); exit(1); } fprintf(_imp_OUTFILE, "%*f", PLACES, R); if (_imp_OutStream == 0) fflush(_imp_OUTFILE); }         // IMPORTANT: See https://gtoal.com/imp77/reference-manual/IO-LIBRARY-EXPERIMENTS.{imp,txt}

_imp_string _imp_substring(_imp_string S, int FROM, int TO) {
    int GET;
    int PUT;
    _imp_string TEMP;
    if (FROM < 0) goto L_0002;
    if (FROM <= *_imp_LENGTH(&S)) goto L_0003;
  L_0002:
    _imp_signal(6, 2, FROM, "substring: from is past end of string");
  L_0003:
    if (TO < 0) goto L_0004;
    if (TO <= *_imp_LENGTH(&S)) goto L_0005;
  L_0004:
    _imp_signal(6, 2, TO, "substring: to is past end of string");
  L_0005:
    if (FROM <= TO) goto L_0006;
    _imp_signal(5, 3, 0, "substring inside-out");
  L_0006:
    *_imp_LENGTH(&TEMP) = (TO - FROM) + 1;
    PUT = 1;
    GET = FROM;
  L_0007:
    if (GET > TO) goto L_0008;
    *_imp_CHARNO(&TEMP, PUT) = *_imp_CHARNO(&S, GET);
    PUT = PUT + 1;
    GET = GET + 1;
    goto L_0007;
  L_0008:
    return TEMP;
    ;
}
_imp_string _imp_fromstring(_imp_string S, int FROM, int TO) { return _imp_substring(S, FROM, TO); }
_imp_string _imp_trim(_imp_string S, int MAX) {
  if (MAX >= 0) goto L_0002;
  _imp_signal(6, 2, MAX, "");
  L_0002:
  if ( /*%map*/ * _imp_LENGTH( &S) <= MAX) goto L_0003;
  /*%map*/ *_imp_LENGTH( &S) = MAX;
L_0003:
  return S;
}

_imp_string _imp_time(void) {
  char temp[256];
  time_t rawtime;
  struct tm *ptm;
  
  if ((rawtime=time(NULL)) == -1)        { _imp_signal(0,0,0, "time() failed"); exit(EXIT_FAILURE); }
  if ((ptm=localtime(&rawtime)) == NULL) { _imp_signal(0,0,0, "localtime() failed"); exit(EXIT_FAILURE); }
  strftime(temp, 255, "%T", ptm);
  return _imp_c2istr(temp);
}

_imp_string _imp_date(void) {
  char temp[256];
  time_t rawtime;
  struct tm *ptm;

  if ((rawtime=time(NULL)) == -1)        { _imp_signal(0,0,0, "time() failed"); exit(EXIT_FAILURE); }
  if ((ptm=localtime(&rawtime)) == NULL) { _imp_signal(0,0,0, "localtime() failed"); exit(EXIT_FAILURE); }
  strftime(temp, 255, "%D", ptm);
  return _imp_c2istr(temp);
}

int _imp_cputime(void) {
  return clock();
}

_imp_string _imp_cliparam(void) {
#ifdef IMP_SOURCE
  %external %string (255) %fn  CLI PARAM %alias "_imp_cliparam"
    %string (255) result = ""
    %integer p
    %for p = 1, 1, getargcount-1 %cycle
      %if length(result)+length(getarg(p)) > 255 %start
        %result = result.substring(getarg(p),1,255-length(result))
        ! Silently truncate, or %signal an overflow event?
      %finish
      result <- result.getarg(p)
      result <- result." " %if p < getargcount-1 %and length(result) <= 254
    %repeat
    %result = result
  %end
                                                          
#endif
  _imp_string tmp;
  *_imp_LENGTH(&tmp) = 0;
  if (_imp_ARGC() >= 2) {
    _imp_strcpy(&tmp, _imp_ARGV(1));
  }
  for (int arg = 2; arg < _imp_ARGC(); arg++) {
    _imp_string each;
    _imp_strcpy(&each, _imp_ARGV(arg));
    if (*_imp_LENGTH(&tmp)+1+*_imp_LENGTH(&each) < 255) {
      _imp_strcat(&tmp, _imp_str_literal(" "));
      _imp_strcat(&tmp, each);
    } else break;
  }
  return tmp;
}

// Awkward name clash between different Imp versions. In one, EVENT is a record;
// in another, EVENT is an integerfn returning what the other calls EVENT_EVENT
// We might be able to hackily support both by using a macro for the function style
// one?

int _imp_subevent(void) {
  return _imp_Event.SUBEVENT;
}

int _imp_eventinfo(void) {
  return _imp_Event.EXTRA;
}

_imp_string _imp_eventmessage(void) { return _imp_Event.MESSAGE; }

_imp_string _imp_itos(int V, int P) { // stolen from IMP.  Should replace with a native C version
  //char temps[256];
  //sprintf(temps, "%*d", Places, I);
  // +1? I have a program somewhere that validates against various original Imp versions.
  //return _imp_c2istr(temps);

int VV;
int Q;
int POS;
                                                                //      6    %byteintegerarray store(0:127)
unsigned char STORE[(127)-(0)+1];
                                                                //      7    
                                                                //      8    vv = v;  vv = -vv %if vv > 0
VV = V;
if (VV <= 0) goto L_0002;
VV = (-(VV));
L_0002:
                                                                //      9    pos = 127
POS = 127;
                                                                //     10    %while vv <= -10 %cycle
L_0003:
if (VV > (-(10))) goto L_0004;
                                                                //     11      q = vv//10
Q = (int)(VV) / (int)(10);
                                                                //     12      store(pos) = q*10-vv+'0';  pos = pos-1
STORE[POS] = (Q * 10) - VV + 48;
POS = POS - 1;
                                                                //     13      vv = q
VV = Q;
                                                                //     14    %repeat
goto L_0003;
L_0004:
                                                                //     15    store(pos) = '0'-vv
STORE[POS] = 48 - VV;
                                                                //     16    %if p <= 0 %start
if (P > 0) goto L_0006;
                                                                //     17      p = 128+p
P = 128 + P;
                                                                //     18    %else
goto L_0007;
L_0006:
                                                                //     19      p = 128-p
P = 128 - P;
                                                                //     20      p = pos %if p > pos
if (P <= POS) goto L_0008;
P = POS;
L_0008:
                                                                //     21      p = p-1
P = P - 1;
                                                                //     22    %finish
L_0007:
                                                                //     23    pos = pos-1 %and store(pos) = '-' %if v < 0
if (V >= 0) goto L_0009;
POS = POS - 1;
STORE[POS] = 45;
L_0009:
                                                                //     24    %while pos > p %and pos > 1 %cycle
L_000a:
if (POS <= P) goto L_000b;
if (POS <= 1) goto L_000b;
                                                                //     25      pos = pos-1;  store(pos) = ' '
POS = POS - 1;
STORE[POS] = 32;
                                                                //     26    %repeat
goto L_000a;
L_000b:
                                                                //     27    pos = pos-1;  store(pos) = 127-pos
POS = POS - 1;
STORE[POS] = 127 - POS;
                                                                //     28    %result = string(addr(store(pos)))
return  /*%map*/ * _imp_STRING(_imp_ADDR(&STORE[POS])); // not sure why gcc wrongly says STORE is uninitialised :-(
}

#ifndef PARM_OPT
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> // for readlink

static void __attribute__ ((noinline)) _imp_report_unass_error(char *vs) {

  // This could eventually be moved to the default signal handler, but for
  // now we'll skip raising a signal and report it directly.  No use yet of
  // libbacktrace, but just reporting the line and file is going to be
  // pretty helpful.
  
  void *caller_addr = __builtin_return_address(1);
  char exe_path[1024];
  char addr_str[32];
  FILE *pipe;
  char func_line[512] = { '\0' };
  char loc_line[256];
  ssize_t len;
  char cmd[2048];

  // or could use global imp_argv[0] below:
  if ((len = readlink("/proc/self/exe", exe_path, sizeof(exe_path)-1)) == -1) { perror("readlink"); exit(1); }
  exe_path[len] = '\0';

  sprintf(addr_str, "0x%lx", (unsigned long)caller_addr);

  snprintf(cmd, sizeof(cmd), "addr2line -e %s -f %s", exe_path, addr_str);
  if (!(pipe = popen(cmd, "r"))) { perror("popen"); exit(1); }

  char *rc=fgets(func_line, sizeof(func_line), pipe);(void)rc;
  if (fgets(loc_line, sizeof(loc_line), pipe)) {
    loc_line[strcspn(loc_line, "\n")] = 0;
    fprintf(stderr, "\n* Monitor entered from IMP - %s has never been assigned a value\n", vs);
  }
  pclose(pipe);

  _imp_generic_backtrace();
  
  exit(1);
}

void _imp_report_range_error(int ix, int low, int high, char *arrayname) {

  // This could eventually be moved to the default signal handler, but for
  // now we'll skip raising a signal and report it directly.  No use yet of
  // libbacktrace, but just reporting the line and file is going to be
  // pretty helpful.
  
  void *caller_addr = __builtin_return_address(1);
  char exe_path[1024];
  char addr_str[32];
  FILE *pipe;
  char func_line[512] = { '\0' };
  char loc_line[256];
  ssize_t len;
  char cmd[2048];

  // or could use global imp_argv[0] below:
  if ((len = readlink("/proc/self/exe", exe_path, sizeof(exe_path)-1)) == -1) { perror("readlink"); exit(1); }
  exe_path[len] = '\0';

  sprintf(addr_str, "0x%lx", (unsigned long)caller_addr);

  snprintf(cmd, sizeof(cmd), "addr2line -e %s -f %s", exe_path, addr_str);
  if (!(pipe = popen(cmd, "r"))) { perror("popen"); exit(1); }

  char *rc=fgets(func_line, sizeof(func_line), pipe);(void)rc;
  if (fgets(loc_line, sizeof(loc_line), pipe)) {
    loc_line[strcspn(loc_line, "\n")] = 0;
    fprintf(stderr, "\n* Monitor entered from IMP - index %d out of range for array %s(%d:%d)\n", ix, arrayname, low, high);
  }
  pclose(pipe);

  _imp_generic_backtrace();
  
  exit(1);  
}

// Calls to these procedures are inserted in the user program so the call itself should be kept short!
// I.e. minimise unnecessary extra parameters and runtime overhead.  Can be made inline for a marginal
// speed gain at the expense of a marginal size loss.  If inlined, these have to be in perms.h not perms.c
int __attribute__ ((noinline)) _imp_rcheck1d(int ix, int low, int high, char *arrayname) {
  if (ix < low || ix > high) _imp_report_range_error(ix, low, high, arrayname);
  return ix;
}

#ifdef NEVER // An earlier version of imptoc used this very similar code...:
int __attribute__ ((noinline)) _C(int x, char *name, int lower, int upper, char *filename, int line) {
  if ((x < lower) || (x > upper)) {
    fprintf(stderr, "* Array bounds exceeded in %s at line %d: %s[%d] is outside %s[%d:%d]\n",
            filename,  line,
            name, x,          name, lower, upper);
    exit(4);
  }
  //fprintf(stderr, "\"%s\", Line %d: %s[%d]\n", filename, line, name, x);
  return x;
}
#endif

int __attribute__ ((noinline)) _imp_ucheck_int(int v, char *vs) {
  int unass; // trying to be a little bit portable rather than hard-coding 0xfefefefe
             // but this does risk checking against some random value (which might turn
             // out to be '0' if not compiled with -ftrivial-auto-var-init=pattern )
  if (v == unass) _imp_report_unass_error(vs); // i2c users should ignore this warning.  Trust me, it's good.
  return v;
}

// because this is inserted into machine generated code,
// we know that 'v' will always be an atom, not an expression:

#define _R(ix, low, high, vs) _imp_rcheck1d(ix, low, high, vs)
//#define _U(v) _imp_ucheck_int(v, #v)
#define _U(v) ((typeof(v))_imp_ucheck_int(v, #v))
#define _US(v,S) _imp_ucheck_int(v, S)

#else

#define _R(ix, low, high, vs) (ix)
#define _U(v) (v)
#define _US(v,S) (v)

#endif
