/*===========================================================================
  Copyright (C) 1995-2010 European Southern Observatory (ESO)
 
  This program is free software; you can redistribute it and/or 
  modify it under the terms of the GNU General Public License as 
  published by the Free Software Foundation; either version 2 of 
  the License, or (at your option) any later version.
 
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
 
  You should have received a copy of the GNU General Public 
  License along with this program; if not, write to the Free 
  Software Foundation, Inc., 675 Massachusetts Ave, Cambridge, 
  MA 02139, USA.
 
  Correspondence concerning ESO-MIDAS should be addressed as follows:
	Internet e-mail: midas@eso.org
	Postal address: European Southern Observatory
			Data Management Division 
			Karl-Schwarzschild-Strasse 2
			D 85748 Garching bei Muenchen 
			GERMANY
===========================================================================*/

/*++++++++++++++++++++
.TYPE		Module
.IDENTIFICATION ost
.AUTHOR    	Guido Russo [ST-ECF], Francois Ochsenbein [ESO-IPG]
.LANGUAGE  	C
.KEYWORDS	Terminal i/o
.ENVIRONMENT	POSIX complaint.
.COMMENTS	Adapted from Toolpack TIE WINDOWS.		
		There is a switch here, named TIMEOUT, for BSD version,
		which inhibates the timeout in ostread.
.VERSION 1.0	30-Jan-1986:	Creation
.VERSION 1.6	16-Jun-1987: Adapted for Ibm-PC, G. Russo, M. Albrecht.
.VERSION 1.7	22-Jun-1987: Adapted for UNIX		
.VERSION 1.8	19-Jan-1988: Transformed to os MIDAS routines.
.VERSION 2.3	28-Aug-1990: Use file descriptor 2 if 0 or 1 is not a tty.
				Added option 2 for ostraw (only input raw)
.VERSION 2.6	06-Nov-1990: Added ostwinch (Window Change)
.VERSION 4.0	05-Apr-1993: POSIX compliant.

 100422		last modif
----------------------------------------------------------------------------*/

/*
 * signal.h produces compilation error if POSIX_SOURCE is defined on Solaris 
 * 2.3 (I believe a bug). If this happens, remove the POSIX_SOURCE definition
 * or move the "#include signal.h" before the POSIX_SOURCE.
 */

/*
 * Define _POSIX_SOURCE to indicate 
 * that this is a POSIX program
 */
#define _POSIX_SOURCE 1

#include <termios.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <setjmp.h>
#include <errno.h>
#include <proto_os.h>		/* ANSI-C prototyping */

#include <osterm.h>			/* Terminal Definitions	*/

#ifdef OSERROR_D        		/* oserror already defined (Silicon G)*/
#define oserror midaserror
#endif

extern char *oserrmsg;
extern int oserror;

void winsize();
char *getenv();
/* int ospexit(); */
int oscopy(/*dest, source, len*/);
typedef void (*FUNCTION)();		/* Pointer to Function	*/

#define MIN(x,y)  ((x) <= (y) ? (x):(y)) /* Minimum */
#define WAITING		1		/* 1s of Timeout */
#define AHEAD		127		/* Size of type-ahead buffer */


/* sigjmp_buf is not defined in Linux SLS 1.03 0.99pl9 
 * but it is defined in Linux SLACKWARE 1.2, 0.99pl12 
 * We can not use jmp_buf, because in Linux it does not save the sig_mask 
 */
static sigjmp_buf  env; 
static char     buf_name[24];
static struct 	termios tty0;
static struct 	termios tty1;
static char ahead_buffer[AHEAD] = "";
static int nahead = 0;			/* Number of char. in ahead_buffer */
static char opened_term = 0;
static int  mytty = STDERR_FILENO;	/* File Descriptor used as terminal */
static int  myout = STDERR_FILENO;	/* File Descriptor used as output   */

#ifdef __STDC__
/* ARGSUSED */
static void ostalarm(int sig)
#else
static void ostalarm(sig)
/*++++++++++++++++
.PURPOSE Function called at end of time-out
.RETURNS Within ostread function. Internal use only.
---------*/
int sig;
#endif
{
  siglongjmp(env,1);	/* return with the saved signal mask from sigsetjmp */
}

/*static void oststop(s) */
void oststop(s)
/*++++++++++++
.PURPOSE Terminate program in case of interrput
.RETURNS To OS
----------------------------------------------------------------------------*/
int s;	/* IN: Signal identification */
{	
  static	char msg[] = "\r\n**** Killed by signal ";
  static  char asig[]= " (XX)\r\n\n";
  char	*p;

  oserror = 0;

  (void) ostclose();
  
  (void) write(myout, msg, sizeof(msg)-1);
  switch(s) {
#ifdef SIGBUS
    case SIGBUS: 	p = "BUS";	break;
#endif
    case SIGPIPE: 	p = "PIPE";	break;
    case SIGTSTP: 	p = "TSTP";	break;
    case SIGTERM: 	p = "TERM";	break;
    case SIGHUP: 	p = "HUP";	break;
    case SIGALRM: 	p = "ALARM";	break;
    default: 		p = ""; break;
  }
  (void) write(myout, p, strlen(p));
 
  asig[2] = '0' + s/10;
  asig[3] = '0' + s%10;
  (void) write(myout, asig, sizeof(asig)-1);

  ospexit(oserror);
}

static FUNCTION f_int = oststop;	/* Interrupt function 	*/


#ifdef __STDC__
static int inhibit(void)
#else
static int inhibit()
/*+++++++++++
.PURPOSE Inhibit the Quit and SUSP keyword
.RETURNS 0 (sucess) / -1 (failure)
-------------*/
#endif
{

/* 
 * 930630: On PC_SCO _POSIX_VDISABLE is defined as (unsigned char)-1 
 * which can not be used in the next condition inclusion. 
 */
#if defined(PC_SCO)
# undef _POSIX_VDISABLE
# define _POSIX_VDISABLE -1
#endif

#if defined(_POSIX_VDISABLE)
  if (_POSIX_VDISABLE != -1) {

  /* The symbol is defined so we can just use it */
  tty1.c_cc[VQUIT] = _POSIX_VDISABLE;
  tty1.c_cc[VSUSP] = _POSIX_VDISABLE;

  } else
#endif
  {
  long temp;

  errno = 0;	/* Make sure we can tell if fpathconf() changes errno. */
  /* 
   * See if it is defined for the terminal 
   */
  temp = fpathconf(mytty,_PC_VDISABLE);
  if (temp != -1) {       /* temp != -1. This is the value to use */
    tty1.c_cc[VQUIT] = (cc_t)temp;
    tty1.c_cc[VSUSP] = (cc_t)temp;
    return(0);
    }
  /* 
   * We get here if fpathconf() returned -1. If
   * errno is changed then ther was a real error
   */
  if (errno != 0) return(-1);
  /* We get here if we cannot disable the suspend character.
   * Fall back on the unlikely character.
   */
  tty1.c_cc[VQUIT] = 0377;
  tty1.c_cc[VSUSP] = 0377;

  }

return(0);
}

int ostopen ()
/*+++++++++++
.PURPOSE Initialize the terminal. 
.RETURNS 0 (sucess) / -1 (failure), oserror explains
.REMARKS If no terminal attached, returns -1.
-------------*/
{
  struct sigaction act, oact;
  oserror = 0;

  /* 
   * Get the terminal status
   */
  if (!isatty(STDERR_FILENO)) {
	mytty = STDIN_FILENO, myout = STDOUT_FILENO;
	if (isatty(mytty) && isatty(myout)) ;
	else {	
		oserror = -1, oserrmsg = "No attached terminal";
		return(-1);
	}
  }
  
  if (tcgetattr(mytty,&tty0) != 0) 	{ oserror = errno; return(-1); }
  (void) tcgetattr(mytty,&tty1);

  /* 
   * Change Echo and Raw modes	
   */
  tty1.c_lflag &= ~(ICANON|ECHO);
  tty1.c_iflag &= ~(ICRNL);	/* Don't change CR to NL 	*/
  tty1.c_cc[VMIN] = 0;		/* Length of type-ahead buffer 	*/
  tty1.c_cc[VTIME] = WAITING*10;/* Waiting time in .1 sec */
  /* 
   * Inhibate Quit and SUSP characters 
   */
  if (inhibit() != 0)  { oserror = errno; return(-1); }		

#if DEBUG
  printf("Operation on files: input=%d / output=%d\n", mytty, myout);
  printf("Initial parameters: oflag=%-7o VMIN=%3d VTIME=%3d\n",
  	tty0.c_oflag, tty0.c_cc[VMIN], tty0.c_cc[VTIME]);
  printf(" .. will be set to:       %-7o      %3d       %3d\n",
  	tty1.c_oflag, tty1.c_cc[VMIN], tty1.c_cc[VTIME]);
  ospwait(20);
#endif

  act.sa_handler = oststop;
  sigemptyset(&act.sa_mask);
  act.sa_flags = 0;
  if (sigaction(SIGPIPE,&act,&oact) != 0) { oserror = errno; return(-1); }
  if (sigaction(SIGTSTP,&act,&oact) != 0) { oserror = errno; return(-1); }
  if (sigaction(SIGHUP,&act,&oact)  != 0) { oserror = errno; return(-1); }
  if (sigaction(SIGTERM,&act,&oact) != 0) { oserror = errno; return(-1); }
  if (sigaction(SIGQUIT,&act,&oact) != 0) { oserror = errno; return(-1); }
#ifdef SIGBUS	/* No-POSIX signal */
  if (sigaction(SIGBUS,&act,&oact) != 0)  { oserror = errno; return(-1); }
#endif
  /* 
   * Define the trap routine in case of Interrupt 
   */
  act.sa_handler = f_int;
  if (sigaction(SIGINT,&act,&oact) != 0)  { oserror = errno; return(-1); }
  /* 
   * By default ignore ALRM signals
   * Otherwise it could be killed by intermail() - /monit/prepa.c
   */
  act.sa_handler = SIG_IGN;
  if (sigaction(SIGALRM,&act,&oact) != 0) { oserror = errno; return(-1); }



  if (tcsetattr(mytty,TCSAFLUSH, &tty1))  { oserror = errno; return(-1); }

  opened_term = 1;

#if DEBUG
  (void) write(myout,"ostopen executed\r\n", 18);
#endif
  return(0);
}

int ostclose()
/*+++++++
.PURPOSE Close the terminal (reset it to normal)
.RETURNS 0 / -1 on failure 
--------------*/
{	
  oserror = 0;

#if DEBUG
  printf("\nostclose: were : VMIN=%d,VTIME=%d. =>(init) %d, %d\n\r",
 	tty1.c_cc[VMIN], tty1.c_cc[VTIME],
 	tty0.c_cc[VMIN], tty0.c_cc[VTIME]);
#endif

  if (opened_term)  /* Reset Terminal	*/
    if (tcsetattr(mytty,TCSAFLUSH, &tty0)) { oserror = errno; return(-1); }

  opened_term = 0;

  return(0);
}

int ostset(tstat)
/*++++
.PURPOSE Set Terminal.
.RETURNS 0 / -1
.REMARKS Only interrupt / quit controls are reset.
---------------------------------*/
struct termstatus *tstat;  /* IN: Status structure with controls to modify */
{	
  oserror = 0;

  if (isatty(mytty) && isatty(myout)) {
    if (tcgetattr(mytty,&tty1) != 0) 	   { oserror = errno; return(-1); }
    tty1.c_cc[VINTR] = tstat->cc_INT;
    tty1.c_cc[VQUIT] = tstat->cc_QUIT;
    if (tcsetattr(mytty,TCSANOW, &tty1)) { oserror = errno; return(-1); }
    } 
  else {
    oserror = -1; 
    oserrmsg = "Can't change terminal setup";
    return(-1);
    }
  return(0);
}

int ostraw(op)
/*++++
.PURPOSE Set Terminal to raw (op = 1), to Normal (0), to OutputRaw (2)
.RETURNS 0 / -1
.REMARKS The 2 mode (OutputRaw) sets VTIME to 0 and VMIN to 1 (untimeout).
---------------------------------*/
int	op;	/* IN: 0 to reset to standard, 1 to reset to raw */
{	
  oserror = 0;

  if (tcgetattr(mytty,&tty1) != 0) 	{ oserror = errno; return(-1); }

#if DEBUG
  printf("ostraw: op=%d. Initial: VMIN=%d,VTIME=%d. Current %d,%d.",op,
 	tty0.c_cc[VMIN], tty0.c_cc[VTIME],
 	tty1.c_cc[VMIN], tty1.c_cc[VTIME]);
#endif
  switch(op) {
    default:				/* ==> to RAW mode	*/
  	tty1.c_lflag &= ~(ICANON|ECHO);
	tty1.c_iflag &= ~ICRNL;		/* Don't change CR to NL        */
	tty1.c_cc[VMIN] = 0;		/* Length of type-ahead buffer 	*/
	tty1.c_cc[VTIME] = WAITING*10;	/* Waiting time in .1 sec */
        break;
    case 0:				/* ==> to NORMAL mode	*/
	tty1.c_cc[VMIN]  = tty0.c_cc[VMIN] ; 
	tty1.c_cc[VTIME] = tty0.c_cc[VTIME]; 
       	tty1.c_lflag = tty0.c_lflag;
	tty1.c_iflag = tty0.c_iflag;	
        break;
    case 2:				/* ==> Untimeout input  */
  	tty1.c_lflag &= ~(ICANON|ECHO);
	tty1.c_iflag &= ~ICRNL;		/* Don't change CR to NL        */
	tty1.c_cc[VMIN] = 1;		/* Length of type-ahead buffer 	*/
	tty1.c_cc[VTIME] = 0;		/* No timer */
        break;
  }
# if DEBUG
  printf("Now %d,%d\n",
 	tty1.c_cc[VMIN], tty1.c_cc[VTIME]);
# endif

  if (tcsetattr(mytty,TCSANOW, &tty1))  { oserror = errno; return(-1); }

  return(0);
}

FUNCTION ostint(f)
/*++++
.PURPOSE Define a function for interrupt
.RETURNS Previous function
----------------------------------------------------------------------------*/
FUNCTION f;	/* IN: Function to call in case of Interrupt */
{	
  struct sigaction act, oact;

  oserror = 0;

  act.sa_handler = f;
  sigemptyset(&act.sa_mask);
  act.sa_flags = 0;
  if (sigaction(SIGINT,&act,&oact) != 0) { oserror = errno; return(SIG_ERR); };
  f_int = f;
  return(oact.sa_handler);
}

FUNCTION ostwinch(f)
/*++++
.PURPOSE Define a function for Window Change
.RETURNS Previous function
----------------------------------------------------------------------------*/
FUNCTION f;	/* IN: Function to call in case of Window Change */
{	
  struct sigaction act, oact;

  oserror = 0;

#ifdef SIGWINCH
  act.sa_handler = f;
  sigemptyset(&act.sa_mask);
  act.sa_flags = 0;
  if (sigaction(SIGWINCH,&act,&oact) != 0) { oserror = errno; return(SIG_ERR); }
  return(oact.sa_handler);
#else
# ifdef ENOSYS
  oserror = ENOSYS;
# else
  oserror = -1, oserrmsg = "WINCH not available";
# endif
  return(SIG_ERR);
#endif
}

int ostin()
/*+++
.PURPOSE Retrieve the number of characters waiting for input (type-ahead buffer)
.RETURNS Number of bytes waiting in type-ahead buffer / -1 for error.
.REMARKS Only possible when terminal is in RAW mode.
--------*/
{
  int icanon=0;
# if DEBUG
  printf("Now %d,%d\n",
 	tty1.c_cc[VMIN], tty1.c_cc[VTIME]);
# endif

  oserror = 0;

  if (nahead < 0)	nahead = 0;
  if (nahead == 0) {	
    /* If terminal in ICANON mode then set to RAW */
    if (tty1.c_lflag & ICANON) {
      icanon=1;
      ostraw(1);
      }
    /* with VMIN & VTIME to 0, read() returns as much data without waiting */
    tty1.c_cc[VTIME] = 0;
    if (tcsetattr(mytty,TCSANOW, &tty1)) 	{ oserror = errno; return(-1); }
    if ((nahead = read(mytty, ahead_buffer, AHEAD)) < 0) oserror = errno;
    /* Reset to previous values */
    if (tcsetattr(mytty,TCSANOW, &tty1)) 	{ oserror = errno; return(-1); }
    if (icanon) ostraw(0);	/* set back to ICANON mode */
    }
  return(nahead);
}

int ostread(string, length, timeout)
/*+++
.PURPOSE Input from the terminal (a timeout is REQUIRED!).
	 A null timeout performs the reading of what is in the
	 type-ahead buffer only.

\begin{TeX}
	 This routine must perform binary input --- \ie the input is not
	 echoed or edited, and the program receives the user's input
	 without having to wait for a RETURN / newline / etc.
	 
	 If the user has not
	 typed anything within the maximum time passed (in seconds and max. 25)
	 it should return with the value zero.
	 It is desirable (but not
	 strictly necessary) that the "wait" be cut short if input is
	 received after the routine call but before the expiry of the
	 time period --- if this is not the case then the calling routines
	 may simply be a bit slow.
\end{TeX}
.RETURNS Number of bytes read / -1 (End of File or Failure)
-----------------------------------------------------------------------*/
char	*string;	/* OUT: string read 		      */
int 	length;		/* IN: size of string 		      */
int	timeout;	/* IN: Timeout time in seconds, max 25 */
{
  register int l;
  unsigned int time;
  struct sigaction act, oact;

  oserror = 0;

  if (length <=0) 		/* Impossible to get 0 char	*/
  { oserror = EINVAL; return(-1); }
				/* Test timeout. Max 25	sec	*/
  time = (timeout < 0 ? 0 : timeout);
  time = (time > 25 ? 25 : time);

  if (nahead > 0) {	
    l = MIN(nahead, length);
    oscopy(string, ahead_buffer, l);
    nahead -= l;	/* Remaining bytes */
    if (nahead)	oscopy(&ahead_buffer[0], &ahead_buffer[l], nahead);
    return(l);
    }


  tty1.c_cc[VTIME] = (unsigned char)time*10;	/* Timeout in seconds */
  if (tcsetattr(mytty,TCSANOW, &tty1)) 	{ oserror = errno; return(-1); }

  act.sa_handler = ostalarm;
  sigemptyset(&act.sa_mask);
  act.sa_flags = 0;
  (void) sigaction(SIGALRM,&act,&oact);
  if ( sigsetjmp(env,1) == 0 ) {	
    for (;;) {
      if ( (l = read(mytty, string, length)) < 0) {
        if (errno == EINTR) continue;
        else oserror = errno; 
        }
      break;
      }
    }
  else   l = 0;				/* Was interrupted */
  (void) sigaction(SIGALRM,&oact,&act); /* set SIGALRM to previous value */

  tty1.c_cc[VTIME] = WAITING*10;	/* Reset 1s time-out	*/
  if (tcsetattr(mytty,TCSANOW, &tty1)) 	{ oserror = errno; return(-1); }

  return(l);
}


int ostwrite(string, length)
/*+++
.PURPOSE Physical output of a string to the terminal.
.RETURNS Number of bytes written / -1 (failure, oserror explains)
-----------------------------------------------------------------------------*/
char	*string;		/* IN: string to be written */
int	length;			/* IN: length of string */
{
  register int l;

  oserror = 0;

  if ((l = write(myout, string, length)) < 0) { oserror = errno; return(-1); }
  return(l);
}

int ostinfo(tstat)
/*+++++
.PURPOSE Get info about the terminal in termstatus structure (see osterm.h),
	i.e. terminal name, number of lines, of columns, baud rate,
	and interrupt control characters.
.RETURNS 0 (sucess) / -1 (failure), oserror explains
.REMARKS If no terminal attached, returns -1.
--------------*/
struct termstatus *tstat;	/* OUT: Terminal description */
{
  register speed_t baud;
  register int i;
  register char *p, *q;
  unsigned short col, row;

  oserror = 0;

  /* 
   * Get the terminal status
   */
  if (!isatty(STDERR_FILENO)) {
    mytty = STDIN_FILENO;
    myout = STDOUT_FILENO;
    if (isatty(mytty) && isatty(myout)) ;
    else {
      oserror = -1, oserrmsg = "No attached terminal";
      return(-1);
      }
    }

  if (tcgetattr(mytty,&tty1) != 0)      { oserror = errno; return(-1); }
  baud = cfgetispeed(&tty1);

  switch(baud) { 
    case B150: 	 
    case B200:
    case B134:   i = 150; 	break;
    case B110:   i = 110; 	break;
    case B50:    i = 50 ; 	break;
    case B75:    i = 75 ; 	break;
    case B300:   i = 300; 	break;
    case B600:   i = 600; 	break;
    case B1200:  i = 1200;      break;
    case B1800:  i = 1800;	break;
	default:
    case B2400:  i = 2400; 	break;
    case B4800:  i = 4800; 	break;
    case B9600:  i = 9600; 	break;
    case B19200: i = 19200;	break;
    case B38400: i = 38400; 	break;
  }
  tstat->baud_rate = i;

  tstat->cc_INT   = tty1.c_cc[VINTR];
  tstat->cc_QUIT  = tty1.c_cc[VQUIT];

  winsize(mytty,&col,&row);
  tstat->lines    = row;
  tstat->columns  = col;

  /* 
   * Copy terminal name to buffer 
   */
  p = getenv("TERM");		/* Get Terminal Type	*/
  if (!p)	p = getenv("term");
  if (!p)	p = "unknown";		/* Default terminal name */

  /* for edit/table a VT100 terminal seems to be needed,
     so we cheat a bit...      KB 100422                         */
  if (strcmp(p,"xterm") == 0) (void) strcpy(p,"vt100");
  tstat->termname = p;

  p = tstat->termname;
  i = strlen(p);
  if (i > sizeof(buf_name)-1)	i = sizeof(buf_name)-1;
  for (q = buf_name; --i >= 0; )	*(q++) = *(p++);
  *q = '\0';			/* End Of String */
  tstat->termname      = buf_name;

  return(0);
}
