/*
  Native entries for the NTerminal class

  @author Jeff Schoen
*/

#include "nxm_sys_lib_NTerminal.h"
#include "native.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
/* extern int      errno;  // this is should be defined in <errno.h> */
#include <signal.h>
#include <sys/types.h>

jint nsignals=-1;

/* Function prototypes for getting window row/column count. */
typedef jint int_4; /* JC4: Added this definition to match X-Midas. */
int_4 m_window_rows_(void);
int_4 m_window_columns_(void);
int  m_get_input (const char*, char*, int*, int, int, int);

/*
 * Class:     NTerminal
 * Method:    print
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_nxm_sys_lib_NTerminal_print
        (JNIEnv *env, jobject obj, jstring text) {
  const char *ctext;
  ctext = (*env)->GetStringUTFChars(env, text, 0);
  printf("%s", ctext);
  (*env)->ReleaseStringUTFChars(env, text, ctext);
}

/*
 * Class:     NTerminal
 * Method:    getInput
 * Signature: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_nxm_sys_lib_NTerminal_getInput
        (JNIEnv *env, jobject obj, jstring prompt, jstring seed, jint flags) {
  int len;
  int status;
  char cbuf[1024];
  const char *cprompt;
  const char *cseed;
  cprompt = GetString(prompt);
  cseed   = GetString(seed);
  len     = strlen(cseed);

  memcpy(cbuf, cseed, len);
  status = m_get_input (cprompt, cbuf, &len, strlen(cprompt), 1024, flags);

       if (status ==  0) cbuf[len] = 0;
  else if (status == -1) strcpy(cbuf,"!LAST");
  else if (status ==  1) strcpy(cbuf,"!NEXT");
  else                   strcpy(cbuf,"");
  ReleaseString(prompt,cprompt);
  ReleaseString(seed,cseed);
  return NewString(cbuf);
}

/* control-c signal handler */
void ctrl_C (int signum) {
  /* if user CTRL-C two times before command finishes, then send INTERUPT signal */
  if (++nsignals == 2) {
    signal (SIGINT,SIG_DFL);
  } else if (nsignals == 1) {
    /* the first time CTRL-C is pressed, try to gracefully exit, so print warning message */
    printf("**** Exiting... Press CTRL-C twice to interrupt immediately. ****\n");
  }
}

/*
 * Class:     NTerminal
 * Method:    getInterrupt
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_nxm_sys_lib_NTerminal_getInterrupt
        (JNIEnv *env, jobject obj) {
  if (nsignals<0) {
    signal (SIGINT,ctrl_C);
    nsignals=0;
    // since 3.1.3: force stdout to un-buffered I/O mode otherwise, when output is
    // redirected to a file, it DOES NOT get flushed after a newline under UNIX or Windows.
    // NOTE: not using setvbuf(stdout, NULL, _IOLBF, BUFSIZ); since it causes issues under Windows
    setbuf(stdout, NULL); // force unbuffered I/O for stdout
  }
#if _DOS // Since 3.7.1 NXM-2626 If running in DOS then reinitialize the signal.
  else if(nsignals>0){
    signal (SIGINT,ctrl_C);
  }
#endif
  return nsignals;
}

/*
 * Class:     NTerminal
 * Method:    setInterrupt
 * Signature: ()V
*/
JNIEXPORT void JNICALL Java_nxm_sys_lib_NTerminal_setInterrupt
          (JNIEnv *env, jobject obj, jint value) {
  nsignals=value;
}

/*
 * Class:     NTerminal
 * Method:    getRowCount
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_nxm_sys_lib_NTerminal_getRowCount
        (JNIEnv *env, jobject obj) {
  return (jint)m_window_rows_();
}

/*
 * Class:     NTerminal
 * Method:    getColCount
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_nxm_sys_lib_NTerminal_getColCount
        (JNIEnv *env, jobject obj) {
  return (jint)m_window_columns_();
}

#if _UNIX
#include <unistd.h>
#include <sys/mman.h>           /* for mmap */
#include <sys/termios.h>
#include <sys/wait.h>
#define KEY_ESC 27
#define KEY_INSERT 50
#define KEY_DELETE 51
#define KEY_HOME 72
#define KEY_END 70
#define KEY_UP 65
#define KEY_DOWN 66
#define KEY_RIGHT 67
#define KEY_LEFT 68
#define KEY_QUIT the_term.c_cc[VQUIT]
#define KEY_INTR the_term.c_cc[VINTR]
#define SEND_ESC { putchar(27); putchar(91); }
#define SEND_INSERT { putchar(27); putchar(91); putchar(64); }
#define SEND_CLEAR { putchar(13); putchar(27); putchar(91); putchar(50); putchar(75); }

#elif _DOS
#define getchar getch
#define KEY_ESC -32
#define KEY_INSERT 82
#define KEY_DELETE 83
#define KEY_HOME 71
#define KEY_END 79
#define KEY_UP 72
#define KEY_DOWN 80
#define KEY_RIGHT 77
#define KEY_LEFT 75
#define KEY_QUIT -99
#define KEY_INTR -99
/* TODO: on Windows this should be 3 #define KEY_INTR 3 */
#define SEND_ESC { putchar(-32); }
#define SEND_INSERT { }
#define SEND_CLEAR { int kk; putchar(13); for (kk=l+lp; kk>0; kk--) { putchar(32); } putchar(13);}

#endif

#define iswordch(A) (isalpha(A) || isdigit(A) || (A) == '_')

/*
 *
 * Gets command string from raw-mode terminal using ANSI terminal
 * control strings.  V1.0 by JBM Nov 30, 1990.
 *
 *    function        supported keys
 *    --------------------------------------
 *      UP              up-arrow, ^B, ^P
 *      DOWN            down-arrow, ^N
 *      LEFT            left-arrow, ^D
 *      RIGHT           right-arrow, ^F
 *      INSERT TOGGLE   Ins
 *      BEG OF LINE     ^A
 *      END OF LINE     ^E
 *      REFRESH SCREEN  ^L, ^R
 *      DEL CHAR        Delete
 *      DEL PREV CHAR   BackSpace, ^H
 *      DEL PREV WORD   ^W
 *      DEL LINE        ^U
 *      NEWLINE         Return, ^M, ^J
 *
 * NOTES:
 * prompt =
 * chs    =
 * lp = length of prompt
 * ls = length of chs
 * flags = input mode flags (see description below)
 *
 * flags  mnemonic           description
 * -----------------------------------------------------------------------------
 *  2    INPUT_NO_ESC        Same as INPUT_NORMAL, but do not allow escapes (arrow keys)
 *  1    INPUT_TAB_COMPLETE  Special input mode used by getInputTabComplete(String,String)
 *  0    INPUT_NORMAL        Normal input
 * -1    INPUT_NORMAL        Single character input
 * -2    INPUT_CHAR_CLEAR    Same as INPUT_CHAR, but clear char when entered
 * -3    INPUT_RETURN        Ignore anything except a return (<CR> and/or <NL>) characters.
 */
int  m_get_input (const char *prompt, char *chs, int *len, int lp, int ls, int flags) {
  char            ch;
  int             oldMIN, oldTIME;
  int             insert = 1,           /* boolean flag for insert mode */
                  i,                    /* current cursor location */
                  wsize,                /* size of a deleted word */
                  shifted,              /* # shifted chars (for word delete) */
                  j,                    /* dummy counter */
                  l,                    /* current command string length */
                  inp_stat;             /* get_input status:
                                         *   -1   up arrow was pressed
                                         *   +1   down arrow was pressed
                                         *    0   return (or LF) was pressed
                                         *  -99   QUIT key was pressed */

#if _UNIX
  struct termios  the_term;
  tcflag_t        save_flags;
  /* Save old terminal settings and set up terminal for non-canonical input */
  if (tcgetattr(STDIN_FILENO, &the_term) != 0) { /* fetch tty state */
    perror("tcgetattr: Couldn't read the terminal");
    kill(0, SIGKILL); /* 2011-11-28: otherwise java process can be stuck and taking 100% of CPU (see BUG 2509) */
  }
  else {
    save_flags = the_term.c_lflag;
    the_term.c_lflag = the_term.c_lflag & (!ICANON);

    oldMIN = the_term.c_cc[VMIN];
    the_term.c_cc[VMIN] = 1;

    oldTIME = the_term.c_cc[VTIME];
    the_term.c_cc[VTIME] = 0;

    if (tcsetattr(STDIN_FILENO, TCSANOW, &the_term) != 0)
      perror("tcsetattr: Couldn't set the terminal");
  }
#endif

  /* clear signals */
  if (nsignals>0) nsignals=0;

  /* Write the prompt */
  for (j = 0; j < lp; putchar(prompt[j++]));

  /* Check for single no-echo character input */
  if (flags < 0) {
    chs[0] = getchar();
    l = 1;
    if (flags == -3) {          /* discard input until we get <CR>,<NL>, or Interrupt*/
      while (chs[0] != '\n' && chs[0] != '\r' && chs[0] != KEY_INTR) { chs[0] = getchar(); }
      putchar('\n');              /* send newline to terminal */
    }
    if (flags== -2) SEND_CLEAR;
    inp_stat = 0;               /* same code as <CR> */

    if (chs[0] == KEY_INTR) {
#if _UNIX && !_darwin
      kill(0, SIGINT);        /* Everyone's Ctrl-C handler will be invoked */
      chs[0] = '\n';
#elif _DOS
/* NTN TODO: add -> raise(SIGINT); */
#endif
    }
  }
  else {                        /* perform reading of a line */

    i = l = *len;
    for (j = 0; j < l; putchar(chs[j++]));

    while (1==1) {

      ch = getchar();

      if (ch == 127) ch = 8;   /* BS key comes in as DEL */

      if (ch == KEY_QUIT) {    /* QUIT key (usually ^\) */
        inp_stat = -99;
        break;
      }

      /* INTR key (usually ^C) -- send SIGINT and delete line */
      if (ch == KEY_INTR) {
#if _UNIX && !_darwin
        kill(0, SIGINT);        /* Everyone's Ctrl-C handler will be invoked */
#elif _DOS
/* NTN TODO: add -> raise(SIGINT); */
#endif
        /* NTN: break after CTRL-C so that Shell/Command can try to exit gracefully. */
        /* ch = 21; */        /* Now act like Ctrl-U was pressed */
        break;
      }

      /* Escape sequences */
      if ((ch == KEY_ESC) && (flags != 2)) {
#if _UNIX
        ch = getchar();
        if (ch != 91) continue;
#endif
        ch = getchar();
        /*/ prepend another forward slash to enable debug printout on next line
        printf("getchar() = %d\n", ch);
        //*/
        switch (ch) {
          case (KEY_UP):
            ch = 2;
            break;              /* up-arrow */
          case (KEY_DOWN):
            ch = 14;
            break;              /* down-arrow */
          case (KEY_RIGHT):
            ch = 6;
            break;              /* right-arrow */
          case (KEY_LEFT):
            ch = 4;
            break;              /* left-arrow */
          case (KEY_INSERT):
            ch = getchar();     /* eat tilde */
            ch = 3;
            break;              /* insert */
          case (KEY_DELETE):
            ch = getchar();     /* eat tilde */
            ch = 127;
            break;              /* delete */
          case (KEY_HOME):
//            ch = getchar();     /* eat tilde */
            ch = 1;
            break;              /* insert */
          case (KEY_END):
//            ch = getchar();     /* eat tilde */
            ch = 5;
            break;              /* delete */
          default:
            continue;
        }
      }

      /* Normal printable characters */
      if (isprint(ch) || ((ch=='\t') && (flags==1))) { /* JC4: 29MAY06 Include tabs too */
        if ((i < ls) && !((insert) && (l == ls))) {
          if (insert && i<l) {
            SEND_INSERT;
            for (j = l; j >= i; j--) chs[j+1] = chs[j];
          }
          putchar(ch);
          chs[i] = ch;
          if ((insert) || (!(insert) && (i == l))) l++;
          i++;
        }
        else {
          putchar(7);
        }
        if (ch!='\t' && flags==1) continue;
      }

      /* Insert key */
      if (ch == 3) {
        insert = !insert;
        continue;
      }

      /* Control-A -- beginning of line */
      if (ch == 1) {
        for (;i > 0; i--) {
          putchar(8);
        }
        continue;
      }

      /* Control-D -- move left */
      if (ch == 4 && i > 0) {
        putchar(8);
        i--;
        continue;
      }

      /* Control-E -- end of line */
      if (ch == 5) {
        for (j = i; j < l; putchar(chs[j++]));
        i = l;
      }

      /* Control-F -- forward */
      if (ch == 6 && i < l) {
        putchar(chs[i]);
        i++;
        continue;
      }

      /* BackSpace */
      if (ch == 8 && i > 0 && l > 0) {
        putchar(8);
        i--;
        ch = 127;
      }

      /* Control-W -- delete beginning of line up to cursor */
      if (ch == 23 && i > 0) {
        l -= i;
        for (j = 0; j < l; j++) {
          chs[j] = chs[j+i];
        }
        i = 0;
        ch = 12; /* fall through to refresh */
      }

      /* Control-L or Control-R -- refresh display line */
      if (ch == 12 || ch == 18) {
        SEND_CLEAR;
        for (j = 0; j < lp; putchar(prompt[j++]));
        for (j = 0; j < l; putchar(chs[j++]));
        for (j = l; j > i; j--) putchar(8);
      }

      /* Control-U -- delete line */
      if (ch == 21) {
        for (j = 0; j < l; j++) chs[j] = ' ';
        i = 0;
        l = 0;
        SEND_CLEAR;
        for (j = 0; j < lp; j++) putchar(prompt[j]);    /* redraw prompt */
        continue;
      }

      /* Delete character */
      if (ch == 127 && l-i > 0) {
        l--;
        for (j = i; j < l; j++) {
          chs[j] = chs[j+1];
          putchar(chs[j]);
        }
        putchar(' ');
        for (j = i; j <= l; j++) putchar(8);
      }

      /* Line terminator -- JC4: As of 29MAY06 also checks for tabs */
      if (ch == 10 || ch == 13 || ((ch == '\t') && (flags==1))) { /* completed command string */
        putchar(13);
        putchar(10);
        inp_stat = 0;
        break;
      }

      /* Up & down arrows - leave */
      if (ch == 2 || ch == 16) {/* up arrow was pressed; erase line and * return */
        SEND_CLEAR;
        inp_stat = -1;
        break;
      }
      if (ch == 14) {           /* down arrow was pressed; erase line and * return */
        SEND_CLEAR;
        inp_stat = 1;
        break;
      }

    }

  }

  /* Clear prompt buffer for next call */
  *len = l;
  for (j = l; j < ls; chs[j++] = ' ');

#if _UNIX && !_darwin
  /* Restore terminal to old settings */
  the_term.c_lflag     = save_flags;
  the_term.c_cc[VMIN]  = oldMIN;
  the_term.c_cc[VTIME] = oldTIME;
  if (tcsetattr(STDIN_FILENO, TCSANOW, &the_term) != 0)
    perror("tcsetattr: Couldn't restore the terminal");

  if (inp_stat == -99) {
    /* QUIT key was pressed */
    putchar(13);
    putchar(10);
    kill(0, SIGQUIT);
    return 0;           /* shouldn't even get here */
  }
#endif

  return (inp_stat);    /* -1, 0 or 1 only */
}

/******************************************************************************
 * Methods to get row and column count for the terminal.
 *   Unix versions copied from X-Midas 3.8.3 m$window_rows_ and
 *   m$window_columns_ in unix.c. Windows versions simply return -1.
 ******************************************************************************/
#if _UNIX && !_OSF
#include <sys/ioctl.h>
#include <sys/termios.h>

/* m$window_rows_

  Author:  DTJ
  Revised:  6/3/91

  Description:
    Returns the number of lines in the current window.

  Requires:
    <sys/ioctl.h> or <sys/termios.h>
    <sys/ttycom.h>
*/
int_4 m_window_rows_(void)
{
  struct winsize termstruct;

  ioctl(0, TIOCGWINSZ, &termstruct);
  if (termstruct.ws_row == 0)
    return (24);                /* default for rlogin */
  else
    return (termstruct.ws_row);
}


/* m$window_columns_

  Author:  DTJ  8/24/93

  Description:
    Returns the number of columns in the current window.

  Requires:
    <sys/ioctl.h> or <sys/termios.h>
    <sys/ttycom.h>
*/
int_4 m_window_columns_(void)
{
  struct winsize termstruct;

  ioctl(0, TIOCGWINSZ, &termstruct);
  if (termstruct.ws_col == 0)
    return (80);                /* default for rlogin */
  else
    return (termstruct.ws_col);
}
#else /* NOT _UNIX */
int_4 m_window_rows_(void)    { return -1; }
int_4 m_window_columns_(void) { return -1; }
#endif
