/* Support routines for terminal I/O. This module defines the following
   Fortran-callable routines: GROTER, GRCTER, GRWTER, GRPTER. */

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

#ifdef PG_PPU
#define GROTER groter_
#define GRWTER grwter_
#define GRCTER grcter_
#define GRPTER grpter_
#define GRKTER grkter_
#define GRETER greter_
#else
#define GROTER groter
#define GRWTER grwter
#define GRCTER grcter
#define GRPTER grpter
#define GRKTER grkter
#define GRETER greter
#endif

/* Open a channel to the device specified by 'cdev'.
 *
 * cdev      I    The name of the device to be opened
 * ldev      I    Number of valid characters in cdev
 * groter    O    The open channel number (-1 indicates an error)
 */
int GROTER(cdev, ldev, cdev_len)
     char *cdev; int *ldev;
     int cdev_len;
{
  int fd;        /* The returned file descriptor */
  char name[64]; /* A copy of the given terminal device name */
/*
 * Make a copy of the given file if there is sufficient room in name[].
 */
  if(*ldev <= sizeof(name)-1) {
    strncpy(name, cdev, *ldev);
    name[*ldev] = '\0';
  } else {
    fprintf(stderr, "groter: Terminal file name too long.\n");
    return -1;
  };
/*
 * Open the terminal.
 */
  if((fd = open(name, 2)) == -1) {
    perror(name);
    return -1;
  };
  return fd;
}


/* Close a previously opened channel.
 *
 * fd        I    The channel number to be closed
 */
void GRCTER(fd)
     int *fd;
{
  close(*fd);
  return;
}

/* Write lbuf bytes from cbuf to the channel fd.  Data is written without
 * any formating.
 *
 * fd        I    The channel number
 * cbuf      I    Character array of data to be written
 * lbuf      I/O  The number of bytes to write, set to zero on return
 */
void GRWTER(fd, cbuf, lbuf, cbuf_len)
     int *fd; char *cbuf; int *lbuf; int cbuf_len;
{
   int nwritten = write (*fd, cbuf, *lbuf);
   if (nwritten != *lbuf)
     perror("Error writing to graphics device");
   *lbuf = 0;
   return;
}

/* Write prompt string on terminal and then read response.  This version
 * will try to read lbuf characters.
 *
 * fd        I    The channel number
 * cprom     I    An optional prompt string
 * lprom     I    Number of valid characters in cprom
 * cbuf      O    Character array of data read
 * lbuf    I/O    The number of bytes to read, on return number read
 */
void GRPTER(fd, cprom, lprom, cbuf, lbuf, cprom_len, cbuf_len)
     int *fd; char *cprom; int *lprom; char *cbuf; int *lbuf;
     int cprom_len; int cbuf_len;
{
  char *buff = cbuf;   /* C pointer to FORTRAN string */
  int ndone=0;         /* The number of characters read */
  struct termios term; /* Terminal mode flags */
/*
 * Get the current set of terminal mode flags.
 */
   if(tcgetattr(*fd, &term)==0) {
     struct termios saveterm; /* Saved terminal attributes */
     int ntry;  /* The number of characters still to be read */
     int nread; /* The number of characters read in one iteration */
/*
 * Save the existing terminal mode flags to be restored later.
 */
     saveterm = term;
/*
 * Enable raw single character input.
 */
     term.c_lflag &= ~ICANON;
     term.c_cc[VMIN] = 1;
/*
 * Install the new terminal flags after first waiting for all pending
 * output to be delivered to the terminal and after discarding any
 * lingering input.
 */
     tcsetattr(*fd, TCSAFLUSH, &term);
/*
 * Prompt for input.
 */
     if(*lprom>0) write(*fd, cprom, *lprom);
/*
 * Read up to 'ntry' characters from the terminal.
 */
     ndone = 0;
     ntry = *lbuf;
     do {
       nread = read(*fd, &buff[ndone], ntry);
       ndone += nread;
       ntry  -= nread;
     } while(nread>0 && ntry>0);
/*
 * Restore the previous terminal mode flags.
 */
     tcsetattr(*fd, TCSAFLUSH, &saveterm);
   };
   *lbuf=ndone;
   return;
}


static struct termios saved_attr;
static struct termios term; /* Terminal mode flags */
static int saved_fd=-1;
static int exit_handler=1;
/*
 * Restore the previous terminal mode.
 */
void GRETER(void) {
  if (saved_fd >= 0) {
    tcsetattr(saved_fd, TCSAFLUSH, &saved_attr);
    saved_fd = -1;
  };
  return;
}


/* Read one character from the terminal (with timeout),
 * and set terminal to no canonical, no echo mode.
 *
 * fd        I    The channel number
 * tmo       I    Timeout in tenths of a second
 * cbuf      O    Character array of data read
 */
void GRKTER(fd, tmo, cbuf, cbuf_len)
     int *fd; int *tmo; char *cbuf; int cbuf_len;
{
  int nread;
/*
 * Get the current set of terminal mode flags.
 */
  *cbuf='\0';
  if (*fd != saved_fd) {
    if (saved_fd >= 0) {
      tcsetattr(saved_fd, TCSAFLUSH, &saved_attr);
      saved_fd = -1;
    }
    if(tcgetattr(*fd, &term)!=0) return;
/*
 * Save the existing terminal mode flags to be restored later.
 */
    saved_attr = term;
/*
 * Enable raw single character input.
 */
    term.c_lflag &= ~(ICANON) & ~(ECHO);
    term.c_cc[VMIN] = 1;
    term.c_cc[VTIME] = 0;
/*
 * Install the new terminal flags after first waiting for all pending
 * output to be delivered to the terminal and after discarding any
 * lingering input.
 */
    tcsetattr(*fd, TCSAFLUSH, &term);
    if (exit_handler) {
      atexit(GRETER);
      exit_handler=0;
    }
    saved_fd = *fd;
  }
  if (*tmo != 0) {
    term.c_cc[VMIN] = (*tmo == 0);
    term.c_cc[VTIME] = *tmo;
    tcsetattr(*fd, TCSANOW, &term);
  }
/*
 * Read one character from the terminal.
 */
  nread = read(*fd, cbuf, 1);
  if (nread!=1) *cbuf='\0';
  if (*tmo != 0) {
    term.c_cc[VMIN] = 1;
    term.c_cc[VTIME] = 0;
    tcsetattr(*fd, TCSANOW, &term);
  }
  return;
}
