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

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

char *_imp_current_file = "";
int   _imp_current_line = 0;

char *_imp_Progname = "<imp main program>"; // get from argv[0]

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

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;

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[1].f = stdin;
  _imp_infile[1].fname = _imp_str_literal("<stdin>");

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

  // 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_InFile  = _imp_infile[_imp_InStream=1].f;
  _imp_OutFile = _imp_outfile[_imp_OutStream=1].f;

  _imp_promptstr = _imp_str_literal("Data: ");
}

// croak() is an attempt to enable writing error messages from within
// signal service routines.  Hence why it is very basic.
static inline void croak(const char *msg) {
  write(2, msg, (unsigned int)strlen(msg));
  _exit(1);
}

// NOTE: current code assumes that this is only called once from the C code generated
// for %begin/%endofprogram.  However it is not impossible for externalroutines to be
// called from a C program with no Imp main program.  Unless we add some termination
// code as well, (eg _imp_terminate) we ought to allow for this being called multiple
// times (i.e. any time an external routine is called), and avoid redundant reinitialisations.

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.
  // Now initialise signal mechanism:
  struct sigaction sa;
  sigemptyset(&sa.sa_mask);
  sa.sa_handler = _imp_signal_handler; // in psignal.[hc]
  sa.sa_flags = 0;
  if (sigaction(SIGUSR1, &sa, NULL) < 0) croak("sigaction(SIGUSR1, &sa, NULL) < 0\n");

  // This is where we should also set up a 'catchall' onevent block to
  // output a backtrace for any signals which have not been caught by the Imp code.

  // Actually, no... we are using C's "main()" for our %begin/%endofprogram
  // and calling this at the head of it.  By the time main() is running, any
  // traps in this procedure have been removed on exiting this procedure.

  // The global backtrace handler has to either be in the code that invokes main()
  // *or* at the head of main() itself (which is bad because main() may have its
  // own legitimate imp onevent block) *OR* (best option) Imp's %begin/%endofprogram
  // should be _imp_main and we invoke it from the C main where this initialisation
  // is done and the default signal handler set up.  This would require having i2c
  // write out a main() and a _imp_main() whenever it compiles a top-level %begin.
}

int _imp_in_tty(void) {  // Determine if input stream is interactive
  return isatty(fileno(_imp_InFile));
}

int _imp_out_tty(void) {  // Determine if output stream is interactive
  return isatty(fileno(_imp_OutFile));
}

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!

  // However, linux has mechanisms for testing whether a file is an interactive
  // stream, so I intend to use that to make prompts more general.
  
  // if (_imp_OutStream == 0
  //     && _imp_outfile[0].lastchar == '\n'
  //     && _imp_InStream == 0
  //     && _imp_infile[0].lastchar == '\n') {

  if (_imp_in_tty() &&
      _imp_out_tty() &&
      _imp_infile[_imp_InStream].lastchar == '\n' &&
      _imp_infile[_imp_OutStream].inpos == 0) {
//    _imp_outfile[_imp_OutStream].outpos == 0) {
    _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);
    }
  }
}

void check_instream(const char *fn) {
  if (   _imp_InStream < 0
      || _imp_InStream >= 256
      || _imp_InFile == NULL
      || _imp_infile[_imp_InStream].f == NULL) {
    fprintf(stderr, "Invalid INSTREAM(%d)", _imp_InStream);
    if ((0 <= _imp_InStream) && (_imp_InStream <= 255)) fprintf(stderr, "=%p",  _imp_infile[_imp_InStream].f);
    fprintf(stderr, " was accessed by %s", fn);
    if (_imp_current_file && *_imp_current_file) fprintf(stderr, " from %s", _imp_current_file);
    if (_imp_current_line) fprintf(stderr, " line %d", _imp_current_line);
    fprintf(stderr, "\n");
    exit(1); // TO DO: signal.
  }
}

void check_outstream(const char *fn) {
  if (   _imp_OutStream < 0
      || _imp_OutStream >= 256
      || _imp_OutFile == NULL
      || _imp_outfile[_imp_OutStream].f == NULL) {
    fprintf(stderr, "Invalid OUTSTREAM(%d)", _imp_OutStream);
    if ((0 <= _imp_OutStream) && (_imp_OutStream <= 255)) fprintf(stderr, "=%p",  _imp_outfile[_imp_OutStream].f);
    fprintf(stderr, " was accessed by %s", fn);
    if (_imp_current_file && *_imp_current_file) fprintf(stderr, " from %s", _imp_current_file);
    if (_imp_current_line) fprintf(stderr, " line %d", _imp_current_line);
    fprintf(stderr, "\n");
    exit(1); // TO DO: signal.
  }
}
