// This was support code for the earlier imptoc suite which was used to
// convert pass1.imp into C.  The corresponding code for i2c in is perms.c
// and this code is not expected to be used for *anything* except pass1.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

// impsup.c uses return codes for errors,
// impsup-signals.c issues a signal_event(ev,sub,extra) for errors

#define IMPSIG_BODY 1
#include "impsig.h"    // We don't want onevent blocks in the library code for now.
#undef IMPSIG_BODY

IMP_EVENT EVENT; // global
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 C ... :-)
    // I now have some C backtrace code that works with gcc (uses dwarf tables)
    // so can add that here soon.  Unfortunately I have a suspicion that by the time
    // we've unwound the longjmp chain back to the NULL at the top, the backtrace
    // no longer has anything useful to print.  But I'll try it just in case I'm wrong.
    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 ...
  return 0;
}

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);
}


/*

   Additions for FILE * I/O
   Conventions:

   Input: 0  console or controlling file
   1  stdin
   2  /dev/null
   other:  arbitrary FILE *

   Output: 0 console or log file
   1 stdout
   2 stderr
   other: arbitrary FILE *

   This is a crude hack based on some of the Comp Sci systems.

   These are defaults and will be overridden by use of procedures
   such as "open input" and "open output"

 */
static void _init_streams (void);

FILE *_ttyin;
FILE *_nullin;
FILE *_ttyout;
FILE *_nullout;

static int _initstreams = 0;

FILE *inmap[128];
FILE *outmap[128];

int instream, outstream;

char inputname[256];
char outputname[256];

#ifdef SPACE_SAVER
static char xspace[15][256];
static int point = -1;
#endif

// if we use printf for all writes, it'll be easier to merge
// multiple imp calls into a single printf.
void printsymbol(int c) {
   if (_initstreams == 0) {
      _init_streams ();
   }
   fprintf(outmap[outstream], "%c", c);
}

void skipsymbol(void) {
   int skip;
   if (_initstreams == 0) {
      _init_streams ();
   }
   do { skip = fgetc(inmap[instream]); } while (skip == '\r');
   if (skip == EOF) {
     signal_event(IMPSIG_IO, IMP_EOF_ERROR, instream);
   }
}

// Note we have no 'readch' here which is needed when reading binary streams and does not handle \r\n processing.
void readsymbol_(int *sym) {
   int ch;
   if (_initstreams == 0) {
      _init_streams ();
   }
   do { ch = fgetc(inmap[instream]); } while (ch == '\r');
   if (ch == EOF) {
     signal_event(IMPSIG_IO, IMP_EOF_ERROR, instream);
   }
   *sym = ch;
}

int nextsymbol(void) {
  int sym;
   if (_initstreams == 0) {
      _init_streams ();
   }
   if (ferror(inmap[instream])) signal_event(IMPSIG_IO, IMP_FERROR, instream);
   if (feof(inmap[instream])) {
     signal_event(IMPSIG_IO, IMP_EOF_ERROR, instream);
   }
   // hack to allow for \r\n newlines, which caused %C not to be recogised because it checks NEXTSYMBOL for '\n'.
   do { sym = fgetc(inmap[instream]); } while (sym == '\r');
   if (sym == EOF) {
     signal_event(IMPSIG_IO, IMP_EOF_ERROR, instream);
   }
   ungetc(sym, inmap[instream]);
   return sym;
}

void newline(void) {
   if (_initstreams == 0) {
      _init_streams ();
   }
   fprintf(outmap[outstream], "\n");
   if (ferror(outmap[outstream])) signal_event(IMPSIG_IO, IMP_FERROR, outstream);
}

void space(void) {
   if (_initstreams == 0) {
      _init_streams ();
   }
   fprintf(outmap[outstream], " ");
   if (ferror(outmap[outstream])) signal_event(IMPSIG_IO, IMP_FERROR, outstream);
}

void newlines(int lines) {
   if (_initstreams == 0) {
      _init_streams ();
   }
   while (lines-- > 0) fprintf(outmap[outstream], "\n");
   if (ferror(outmap[outstream])) signal_event(IMPSIG_IO, IMP_FERROR, outstream);
}

void spaces(int sp) {
   if (_initstreams == 0) {
      _init_streams ();
   }
   while (sp-- > 0) fprintf(outmap[outstream], " ");
   if (ferror(outmap[outstream])) signal_event(IMPSIG_IO, IMP_FERROR, outstream);
}

void write(int num, int prec) {
   if (_initstreams == 0) {
      _init_streams ();
   }
   // Despite this being a cool hack, it doesn't match the rather weird Imp write() :-(
   fprintf(outmap[outstream], "%*d", prec, num); // don't forget -num - I think they get space padding on the right
   if (ferror(outmap[outstream])) signal_event(IMPSIG_IO, IMP_FERROR, outstream);
}

void printstring_(char *s) {
   if (_initstreams == 0) {
      _init_streams ();
   }
   fprintf(outmap[outstream], "%s", s);
   if (ferror(outmap[outstream])) signal_event(IMPSIG_IO, IMP_FERROR, outstream);
}

char *tostring(int c) {
#ifdef SPACE_SAVER // Making some big assumptions about how these are used
   point = (point + 1) & 15;
   xspace[point][0] = c;
   xspace[point][1] = '\0';
   return xspace[point];
#else // Safer, but with heap lossage
   char *s = malloc(2);
   if (s == NULL) signal_event(IMPSIG_LIB, IMP_MALLOC_ERROR, 2);
   s[0] = c; s[1] = '\0';
   return s;
#endif
}

char *concat_(char *lhs, char *rhs)
{
#ifdef SPACE_SAVER // Making some big assumptions about how these are used
   point = (point + 1) & 15;
   strcpy (xspace[point], lhs);
   strcat (xspace[point], rhs);
   return xspace[point];
#else // Safer, but with heap lossage
   int requested_length = strlen(lhs)+strlen(rhs)+1;
   char *s = malloc(requested_length);
   if (s == NULL) signal_event(IMPSIG_LIB, IMP_MALLOC_ERROR, requested_length);
   sprintf(s, "%s%s", lhs, rhs);
   return s;
#endif
}
int imp_resolve (char *origlhs, char *dest1, char *exp, char *dest2)
{
   int l1;
   char *s1, *lp;
   char lhs[256]; // malloc it...

   strcpy (lhs, origlhs);
   s1 = strstr (lhs, exp);
   if (s1 == NULL)
      return 1;
   s1[0] = 0;
   l1 = strlen (lhs) + strlen (exp);
   if (dest1 != NULL)
      strcpy (dest1, lhs);
   lp = (char *) lhs + l1;
   if (dest2 != NULL)
      strcpy (dest2, lp);
   return 0;
}
char *imp_tostring (int sym)
{
   static char aa[2];

   aa[0] = sym;
   aa[1] = 0;
   return aa;
}
void imp_stop ()
{
   printf ("\n\n IMP %%stop Called\n\n");
   exit (0);
}
char *imp_substring (char *source, int from, int to)
{
   static char lhs[256];
   int i, j;

   i = 0;
   for (j = from; j <= to; j++)
      lhs[i++] = source[j];
   lhs[i] = 0;
   return lhs;
}
void imp_strjam (char *lhs, char *rhs, int max)
{
   lhs[0] = 0;
   strncat (lhs, rhs, max);
}
void s__cstring (char *impstring, char *cstring)
{
   /* dummy version for when strings are already cstring */
   strcpy (cstring, impstring);
}
char *itos (int val)
{
   static char sp[20];

   sprintf (sp, "%d", val);
   return sp;
}

char *int2ascii(int i, int base, int sp) {
  return "TODO!";
}

void phex (int val)
{
   printf ("%8x", val);
}
char *htos (int val, int places)
{
/*GT: */
   static char sp[9];		/* even as a static this is bad practise */

   switch (places) {
   case 1:
      sprintf (sp, "%1x", val);
      break;
   case 2:
      sprintf (sp, "%2x", val);
      break;
   case 3:
      sprintf (sp, "%3x", val);
      break;
   case 4:
      sprintf (sp, "%4x", val);
      break;
   case 5:
      sprintf (sp, "%5x", val);
      break;
   case 6:
      sprintf (sp, "%6x", val);
      break;
   case 7:
      sprintf (sp, "%7x", val);
      break;
   default:
      sprintf (sp, "%8x", val);
      break;
   }
   return sp;
}

static void _init_streams (void)
{
   if (_initstreams == 1) return;
   _initstreams = 1;		/* Done. */

   inmap[0] = _ttyin = fopen ("/dev/tty", "r");
   inmap[1] = stdin;		/* or /dev/stdin */
   inmap[2] = _nullin = fopen ("/dev/null", "r");

   outmap[0] = _ttyout = fopen ("/dev/tty", "w");
   outmap[1] = stdout;		/* or /dev/stdout */
   outmap[2] = stderr;		/* or /dev/stderr */

   instream = 0;
   outstream = 0;

}

void selectinput (int stream)
{
   if (_initstreams == 0) {
      _init_streams ();
   }
   assert ((((int) stream) >= 0) && (((int) stream) < 128));
   instream = stream;
   if (inmap[stream] == NULL) signal_event(IMPSIG_IO, IMP_FERROR, stream);
}

void selectoutput (int stream)
{
   if (_initstreams == 0) {
      _init_streams ();
   }
   assert ((((int) stream) >= 0) && (((int) stream) < 128));
   outstream = stream;
   if (outmap[stream] == NULL) signal_event(IMPSIG_IO, IMP_FERROR, stream);
}

int openinput (int stream, char *filename)
{
   if (_initstreams == 0) {
      _init_streams ();
   }
   /* Could warn if inmap[stream] != NULL ??? */
   /* Doing a freopen is not quite what's wanted in that case */
   if ((inmap[stream] != NULL) && (inmap[stream] != stdin)) fclose(inmap[stream]);
   inmap[stream] = NULL; 
   inmap[stream] = fopen (filename, "r");
   if (inmap[stream] == NULL) signal_event(IMPSIG_IO, IMP_FOPEN_IN_ERROR, stream);
   return (inmap[stream] != NULL);			/* WHAT SHOULD openinput RETURN??? */
}

int openoutput (int stream, char *filename)
{
   if (_initstreams == 0) {
      _init_streams ();
   }
   /* Could warn if outmap[stream] != NULL ??? */
   /* Doing a freopen is not quite what's wanted in that case */
   if ((outmap[stream] != NULL) && (outmap[stream] != stderr) && (outmap[stream] != stdout)) fclose(outmap[stream]);
   outmap[stream] = NULL; 
   outmap[stream] = fopen (filename, "w");
   if (outmap[stream] == NULL) signal_event(IMPSIG_IO, IMP_FOPEN_OUT_ERROR, stream);
   return (outmap[stream] != NULL);			/* WHAT SHOULD openoutput RETURN??? */
}

void closeinput (void)
{
   int old = instream;

   selectinput (0);
   assert (inmap[instream] != NULL);
   if (old == 0)
      return;			/* Won't allow to close console */
   fclose (inmap[old]);
   inmap[old] = NULL;
}

void closeoutput (void)
{
   int old = outstream;

   selectoutput (0);
   assert (outmap[outstream] != NULL);
   if (old == 0)
      return;			/* Won't allow to close console */
   if ((outmap[old] != NULL) && (outmap[old] != stderr) && (outmap[old] != stdout)) fclose (outmap[old]);
   outmap[old] = NULL;
}

void setinput (int pos)
{
   assert (inmap[instream] != NULL);
   fseek (inmap[instream], pos, SEEK_SET);
}

void setoutput (int pos)
{
   assert (outmap[outstream] != NULL);
   fseek (outmap[outstream], pos, SEEK_SET);
}
