/* $Id: vms_io.c,v 1.32 2024/10/16 00:19:36 tom Exp $ */

#define DEBUG

#include <stdarg.h>
#include <vttest.h>
#include <esc.h>
#include <ttymodes.h>

#include <starlet.h>
#include <lib$routines.h>
#include <stsdef.h>
#include <ssdef.h>
#include <descrip.h>
#include <iodef.h>
#include <ttdef.h>
#include <tt2def.h>
#include <cvtdef.h>   /* for float constants          */

typedef struct {
  unsigned short int status;    /* I/O completion status */
  unsigned short int count;     /* byte transfer count   */
  int dev_dep_data;             /* device dependent data */
} QIO_SB;                       /* This is a QIO I/O Status Block */

#define NIBUF   1024  /* Input buffer size            */
#define NOBUF   1024  /* MM says big buffers win!     */
#define EFN     0   /* Event flag                   */

static char obuf[NOBUF];        /* Output buffer                */
static int nobuf;               /* # of bytes in above          */
static char ibuf[NIBUF];        /* Input buffer                 */
static int nibuf;               /* # of bytes in above          */
static int oldmode[3];          /* Old TTY mode bits            */
static int newmode[3];          /* New TTY mode bits            */
static short iochan;            /* TTY I/O channel              */

static int in_flags;
static int cr_flag = TRUE;

static void
give_up(int status)
{
  if (LOG_ENABLED)
    fprintf(log_fp, NOTE_STR "status=%#x\n", status);
  close_tty();
  exit(status);
}

static int
lookup_speed(int code)
{
  /* *INDENT-OFF* */
  static struct {
    int code;
    int speed;
  } table[] = {
     {TT$C_BAUD_50, 50 }
    ,{TT$C_BAUD_75, 75 }
    ,{TT$C_BAUD_110, 110 }
    ,{TT$C_BAUD_134, 134 }
    ,{TT$C_BAUD_150, 150 }
    ,{TT$C_BAUD_300, 300 }
    ,{TT$C_BAUD_600, 600 }
    ,{TT$C_BAUD_1200, 1200 }
    ,{TT$C_BAUD_1800, 1800 }
    ,{TT$C_BAUD_2000, 2000 }
    ,{TT$C_BAUD_2400, 2400 }
    ,{TT$C_BAUD_3600, 3600 }
    ,{TT$C_BAUD_4800, 4800 }
    ,{TT$C_BAUD_7200, 7200 }
    ,{TT$C_BAUD_9600, 9600 }
    ,{TT$C_BAUD_19200, 19200 }
    ,{TT$C_BAUD_38400, 38400 }
#ifdef TT$C_BAUD_57600
    ,{TT$C_BAUD_57600, 57600 }
    ,{TT$C_BAUD_76800, 76800 }
    ,{TT$C_BAUD_115200, 115200 }
#endif
  };
  /* *INDENT-ON* */

  int n;
  int speed = DEFAULT_SPEED;
  for (n = 0; n < TABLESIZE(table); n++) {
    if (table[n].code == code) {
      if (table[n].speed > DEFAULT_SPEED)
        speed = table[n].speed;
      break;
    }
  }
  return speed;
}

/*
 * Read the tty-in.  If we're looking for a single character, wait. Otherwise,
 * read whatever is available, assuming that it's queued and ready.
 *
 * The VMS terminal driver operates with a one-second timer, and (from the
 * manual) we must give at least two seconds for the timeout value.  If we
 * give a 0-second timeout, we'll only get (from testing) 16 bytes, which
 * is enough for a CPR, but not for DA, etc.
 */
static void
read_vms_tty(int length, int timed)
{
  int status;
  QIO_SB iosb;
  int term[2] =
  {0, 0};
  int my_flags = IO$_READLBLK | in_flags;
  int timeout = 0;

  if (length < 1)
    return;
  if (length > 1) {
    my_flags |= IO$M_TIMED;
    timeout = 1;  /* seconds */
  }
  if (timed)
    timeout = 2;  /* seconds */

#ifdef DEBUG
  if (LOG_ENABLED) {
    fprintf(log_fp, NOTE_STR "reading: len=%d, flags=%#x\n", length, my_flags);
    fflush(log_fp);
  }
#endif
  status = sys$qiow(EFN, iochan, my_flags,
                    &iosb, 0, 0, ibuf, length, timeout, term, 0, 0);
#ifdef DEBUG
  if (LOG_ENABLED) {
    fprintf(log_fp,
            NOTE_STR "read: st=%d, cnt=%#x, dev=%#x\n",
            iosb.status, iosb.count, iosb.dev_dep_data);
    fflush(log_fp);
  }
#endif

  if (status != SS$_NORMAL
      || iosb.status == SS$_ENDOFFILE)
    give_up(status);

  nibuf = iosb.count + (iosb.dev_dep_data >> 16);
}

/******************************************************************************/

void
reset_inchar(void)
{
  /* FIXME */
}

/*
 * Wait until a character is typed on the terminal then read it, without
 * waiting for CR.
 */
char
inchar(void)
{
  int c;

  fflush(stdout);
  read_vms_tty(1, FALSE);
  c = (ibuf[0] & 0xff);
  if (c == '\r' && cr_flag && !(in_flags & IO$M_NOFILTR)) {
    c = '\n';
    if (in_flags & IO$M_NOECHO)
      putchar(c);
  }

  if (active)
    pause_replay();
  if (LOG_ENABLED) {
    fputs(READ_STR, log_fp);
    put_char(log_fp, c);
    fputs("\n", log_fp);
  }
  if (active)
    resume_replay();

  return c;
}

/*
 * Get an unfinished string from the terminal:  wait until a character is typed
 * on the terminal, then read it, and all other available characters.  Return a
 * pointer to that string.
 *
 * This function won't read long strings, but we use it for instances where
 * we only expect short (16 bytes or less) replies (i.e., a function-key).
 */
char *
instr(void)
{
  static char result[BUF_SIZE];

  FILE *save = log_fp;

  pause_replay();
  log_fp = NULL;
  result[0] = inchar();
  zleep(100);   /* Wait 0.1 seconds */
  log_fp = save;

  read_vms_tty(sizeof(result) - 3, FALSE);
  memcpy(result + 1, ibuf, nibuf);
  result[1 + nibuf] = '\0';

  if (LOG_ENABLED) {
    fputs(READ_STR, log_fp);
    put_string(log_fp, result);
    fputs("\n", log_fp);
  }
  resume_replay();

  return (result);
}

/*
 * Get an unfinished string from the terminal:  wait until a character is typed
 * on the terminal, then read it, and all other available characters.  Return a
 * pointer to that string.
 *
 * Unlike 'instr()', this will really read long strings.
 */
char *
get_reply(void)
{
  static char result[256];

  result[0] = inchar();
  zleep(100);   /* Wait 0.1 seconds */
  fflush(stdout);
  pause_replay();

  read_vms_tty(sizeof(result) - 3, TRUE);
  memcpy(result + 1, ibuf, nibuf);
  result[1 + nibuf] = '\0';

  if (LOG_ENABLED) {
    fputs(READ_STR, log_fp);
    put_string(log_fp, result);
    fputs("\n", log_fp);
  }

  resume_replay();

  return (result);
}

/*
 * Read to the next newline, truncating the buffer at BUF_SIZE-1 characters
 */
void
inputline(char *s)
{
  char *result = s;

  if (is_replaying() && (result = replay_string()) != NULL) {
    strcpy(s, result);
    puts(result);
    fflush(stdout);
    zleep(1000);
  } else {
    do {
      int ch;
      char *d = s;
      while ((ch = getchar()) != EOF && ch != '\n') {
        if ((d - s) < BUF_SIZE - 2)
          *d++ = ch;
      }
      *d = 0;
    } while (!*s);
  }

  if (LOG_ENABLED)
    fprintf(log_fp, READ_STR "%s\n", result);
}

/*
 * Flush input buffer, make sure no pending input character
 */
void
inflush(void)
{
  nibuf = 0;
}

void
outflush(void)
{
  fflush(stdout);
}

void
holdit(void)
{
  inflush();
  tprintf("Push <RETURN>");
  readnl();
}

void
readnl(void)
{
  if (is_replaying() && (result = replay_string()) != NULL) {
    puts(result);
    fflush(stdout);
    zleep(1000);
  } else {
    fflush(stdout);
    while (inchar() != '\n') ;
  }
  if (LOG_ENABLED)
    fputs(READ_STR "\n", log_fp);
}

/*
 * Sleep and do nothing (don't waste CPU) for t milliseconds
 */
void
zleep(int t)
{
  float seconds = t / 1000.;
  unsigned flags = 0;
#ifdef USE_IEEE_FLOAT
  unsigned type = CVT$K_IEEE_S;
#else
  unsigned type = CVT$K_VAX_F;
#endif
  lib$wait(&seconds, &flags, &type);
}

/******************************************************************************/

void
init_ttymodes(int pn)
{
  struct dsc$descriptor idsc;
  struct dsc$descriptor odsc;
  char oname[40];
  QIO_SB iosb;
  int status;

  /* *INDENT-EQLS* */
  odsc.dsc$a_pointer = "TT";
  odsc.dsc$w_length  = strlen(odsc.dsc$a_pointer);
  odsc.dsc$b_dtype   = DSC$K_DTYPE_T;
  odsc.dsc$b_class   = DSC$K_CLASS_S;
  idsc.dsc$b_dtype   = DSC$K_DTYPE_T;
  idsc.dsc$b_class   = DSC$K_CLASS_S;

  do {
    /* *INDENT-EQLS* */
    idsc.dsc$a_pointer = odsc.dsc$a_pointer;
    idsc.dsc$w_length  = odsc.dsc$w_length;
    odsc.dsc$a_pointer = &oname[0];
    odsc.dsc$w_length  = sizeof(oname);

    status = lib$sys_trnlog(&idsc, &odsc.dsc$w_length, &odsc);
    if (status != SS$_NORMAL
        && status != SS$_NOTRAN)
      give_up(status);
    if (oname[0] == 0x1B) {
      odsc.dsc$a_pointer += 4;
      odsc.dsc$w_length -= 4;
    }
  } while (status == SS$_NORMAL);

  status = sys$assign(&odsc, &iochan, 0, 0);
  if (status != SS$_NORMAL)
    give_up(status);

  status = sys$qiow(EFN, iochan, IO$_SENSEMODE, &iosb, 0, 0,
                    oldmode, sizeof(oldmode), 0, 0, 0, 0);
  if (status != SS$_NORMAL
      || iosb.status != SS$_NORMAL)
    give_up(status);
#ifdef DEBUG
  if (LOG_ENABLED)
    fprintf(log_fp,
            NOTE_STR "sense: st=%d, cnt=%#x, dev=%#x\n",
            iosb.status, iosb.count, iosb.dev_dep_data);
#endif

  newmode[0] = oldmode[0];
  newmode[1] = oldmode[1] | TT$M_EIGHTBIT;
  newmode[1] &= ~(TT$M_TTSYNC | TT$M_HOSTSYNC);

  /* Note: this assumes we're doing IO$_SETCHAR */
  newmode[2] = oldmode[2] | TT2$M_PASTHRU;

  status = sys$qiow(EFN, iochan, IO$_SETMODE, &iosb, 0, 0,
                    newmode, sizeof(newmode), 0, 0, 0, 0);
  if (status != SS$_NORMAL
      || iosb.status != SS$_NORMAL)
    give_up(status);

  max_lines = (newmode[1] >> 24);
  min_cols = newmode[0] >> 16;
  tty_speed = lookup_speed(iosb.count & 0xff);

  if (LOG_ENABLED) {
    fprintf(log_fp, NOTE_STR "TTY modes %#x, %#x, %#x\n",
            oldmode[0], oldmode[1], oldmode[2]);
    fprintf(log_fp, NOTE_STR "iosb.count = %#x\n", iosb.count);
    fprintf(log_fp, NOTE_STR "iosb.dev_dep_data = %#x\n", iosb.dev_dep_data);
    fprintf(log_fp, NOTE_STR "TTY speed = %d\n", tty_speed);
  }
}

/*
 * Restore state to "normal", for menu-prompting
 */
void
restore_ttymodes(void)
{
  outflush();
  in_flags = 0;
  cr_flag = TRUE;
}

void
close_tty(void)
{
  int status;
  QIO_SB iosb;

  status = sys$qiow(EFN, iochan, IO$_SETMODE, &iosb, 0, 0,
                    oldmode, sizeof(oldmode), 0, 0, 0, 0);
  if (status == SS$_IVCHAN)
    return;     /* already closed it */
  (void) sys$dassgn(iochan);
}

void
set_tty_crmod(int enabled)
{
  cr_flag = enabled;
}

void
set_tty_echo(int enabled)
{
  if (enabled)
    in_flags &= ~IO$M_NOECHO;
  else
    in_flags |= IO$M_NOECHO;
}

void
set_tty_raw(int enabled)
{
  if (enabled)
    in_flags |= IO$M_NOFILTR;
  else
    in_flags &= ~IO$M_NOFILTR;
}
