/* @(#)tvget.c	19.1 (ES0-DMD) 02/25/03 13:58:14 */
/*===========================================================================
  Copyright (C) 1995 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 Massachusetss Ave, Cambridge, 
  MA 02139, USA.
 
  Corresponding 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 tvget.c
.AUTHOR    	Francois Ochsenbein [ESO-IPG]
.LANGUAGE  	C
.KEYWORDS	Terminal Independant i/o Package.
.ENVIRONMENT 	TermWindows
.COMMENTS	Only Input Functions are here.
\begin{TeX}
Used Capabilities:
$$\begin{tabular}{|lp{37em}|}	\hline
{\tt ...}	& and all input keys, \ie 
		  {\tt ku kd kr kl {\it or} kb kh} for arrows,
		  {\tt k1}\dots{\tt k9} for PF keys,
		  {\tt K0}\dots{\tt K9}, {\tt K. K, K- KE} for keypad,
			extended with {\tt Kf, Ki, Kr}\dots for VT200,
		  {\tt F0}\dots {\tt F}$n$ for function keys.	\\
\hline
\end{tabular}$$
\end{TeX}
.VERSION 1.0	07-Jul-1986:	Creation
.VERSION 1.1	12-Aug-1986:	Added tv_wa, writing a string with attributes.

.VERSION 1.2	13-Oct-1986	Some terminals (e.g. HP) need to resend the
			attribute on each new line.
			This feature incorporated with ChangedLine flag
			in standout part of TERM structure.
.VERSION 2.0	03-Dec-1986	New terminal-independant with output buffering.
.VERSION 2.1	11-Jun-1987	Version '2' of TermWindows ---
			Defaut input char is Control_Z
			Redefined interfaces to xv routines, allowing
			a batch mode, or no terminal connected at all.
.VERSION 2.2	23-Jun-1987	Adapted to UNIX and Minimal Implementation.
.VERSION 2.3	02-Dec-1987	Use of external function while waiting for input
.VERSION 3.0	20-Mar-1988	Version '3' of TermWindows.
----------------------------------------------------------------------------*/

#define DEBUG		0		/* For debugging	*/

#define PM_LEVEL 	LEVEL_TV

#define	TW_MACROS	0		/* Don't use TermWindows Macros */
#define	TW_import	0	
#define TW_STRUCT	0	/* Do not use window Structures		*/
#include <twset.h>

MID_EXTERN TERM	*terms ;		

MID_STATIC short int *low = NULL_PTR(short int);
MID_STATIC short int *high= NULL_PTR(short int);/* To scan input sequences */
MID_STATIC  int		TICK = 5;		/* Check input every 5 sec.*/

#if 	DEBUG
#	define ENTER_DEBUG(x)	ENTER(x)
#	define EXIT_DEBUG(x)	EXIT(x)
#	define	TERMTEST	if(!terms->version) tv_gopen();\
				tv_ed(terms)
#else
#	define	TERMTEST	if(!terms->version) tv_gopen()
#	define ENTER_DEBUG(x)	
#	define EXIT_DEBUG(x)	return(x)
#endif

#define FINISH		goto FIN
#define	ENTER_TV(x)	int 	state_buffer;			\
			ENTER(x);	TERMTEST;			\
			state_buffer  =	tv_buffer(1);	
#define	EXIT_TV(f)	FIN:						\
			tv_buffer(state_buffer);			\
			EXIT(f)

		/* Macros just to improve readibility	*/
#define		curl			(terms->pos[0])
#define		curc			(terms->pos[1])
#define		diml			(terms->dim[0])
#define		dimc			(terms->dim[1])

#define		BUFR			(terms->bufr)
#define		bufp			(terms->ir)
#define		buftop			(terms->irtop)
#define		BUFS			(terms->bufs)
#define		CAPLIST			(terms->caplist)
#define		RAW_MODE		(terms->flags & TERM_RAW)
#define		KEYPAD			(terms->flags & TERM_KPAD)


	MONITOR (TVGET);

/*==========================================================================
 *			tv_gto
 *==========================================================================*/
int tv_gto(sec)
/*++++++++++
.PURPOSE Defines the Time-out
.RETURNS Previous time-out
.REMARKS 
--------------*/
	int	sec;	/* IN: The asked time-out (seconds)	*/
{
	int	old;

  old = TICK;
  TICK = MAX(sec, 1);		/* At least 1 second...	*/

  return(old);
}

/*==========================================================================
 *			tv_gaj
 *==========================================================================*/
static int tv_gaj(ch, index)
/*++++++++++
.PURPOSE Modifies low and high limits to match an input sequence.
.RETURNS Length of the lower matching sequence; 0 if no matching.
.REMARKS For internal use (tv_getc) only.
--------------*/
	char	*ch;	/* IN: Character to match			*/
	int	index;	/* IN: Position of the char in the caplist	*/
{
	register char *capl, *capu;
	register int i;
#if DEBUG
	short int *m; char *cap;

  TRACE_ED_STR2("Trying to match char ",ch,1);
  TRACE_ED_I(".... at offset: ", index);
  for (m = low; m <= high; m++)
  {  	cap = *m + CAPLIST;
	TRACE_ED_STR2("...sequence", cap, 10);
  }
#endif

  i = index+4;

  capl = *low + CAPLIST;

  if(*ch < *(capl+i)) 	return(0);	/* Match impossible	*/

  capu = *high + CAPLIST;

#if DEBUG
  TRACE_ED_STR2("Up  sequence is:", capu, 10);
  TRACE_ED_STR2("Low sequence is:", capl, 10);
#endif

  if (*ch > *(capu+i)) return(0);		/* Match impossible	*/

  while (*ch > *(capl+i))	/* Increment the lower limit until match */
	capl = *(++low) + CAPLIST;
  if (*ch != *(capl+i)) return(0);    

  while (*ch < *(capu+i))	/* Decrement the upper limit until match */
	capu = *(--high) + CAPLIST;

#if DEBUG
  TRACE_ED_STR2("Low sequence is:", capl, 10);
  TRACE_ED_STR2("Up  sequence is:", capu, 10);
#endif

  if (*ch != *(capu+i)) return(0);    

  return(*capl);
}

/*==========================================================================
 *			tv_in
 *==========================================================================*/
static int tv_in()
/*++++++++++++
.PURPOSE Read input from terminal
.RETURNS Length of read string / EOF
.REMARKS While the program is waiting for input, the function defined
	by tv_fw is called and executed.
---------------*/
{
	int len, count;
	TVSAVE	saved;
	

  ENTER_DEBUG("+tv_in");

  for (count = 32767; (buftop == 0) && (count > 0); )
  {	if (terms->term_type)	
  		len = osaread(0, BUFR+buftop, terms->buf_size-buftop); 
  				/* Read from File */
	else	len = ostread(BUFR+buftop, terms->buf_size-buftop, TICK); 
				/* Read Terminal  */
	if (len)	break;
	if (terms->fct)					/* Call fw routine */
	{	tv_cus(&saved);	/* .. save cursor */
		count = (*(terms->fct))(count);	
		if (count > 0)	tv_cur(&saved);	/* .. restore cursor */
	}
	else count -= TICK;
  }

  if (len == 0)	len = buftop, buftop=0;	/* Was got via tv_supply */
  if (len < 0)	ERROR(osmsg());
  else if (terms->term_type)		/* Read from File */
  {	if (len >= terms->buf_size)	
  		len = terms->buf_size, ERROR("Input truncated");
  	else 	*(BUFR + len++) = '\r';
  }  

  TRACE_STR2(BUFR, len);
  EXIT_DEBUG(len);
}

/*==========================================================================
 *			tv_inw
 *==========================================================================*/
static int tv_inw(s)
/*+++++++++++
.PURPOSE Read input from terminal with time_out
.RETURNS Length / EOF
.REMARKS Unique interface to xv_cin
-------------*/
	int	s;		/* IN: time-out in seconds		*/
{
	register int len;

  ENTER_DEBUG("+tv_inw");
  
  if (terms->term_type == 0)			/* Actual terminal	*/
  {	len = ostread(BUFR, terms->buf_size, s);
	if (len < 0)	ERROR(osmsg());
	else if (len == 0) 	len = buftop;	/* Was got via tv_supply */
	TRACE_STR2(BUFR, len);
	EXIT_DEBUG(len);
  }
  EXIT_DEBUG(tv_in());				/* No terminal		*/
}

/*==========================================================================
 *			tv_getc
 *==========================================================================*/
int	tv_getc(ch)
/*+++++++++++++
.PURPOSE Single-character input routine from the terminal; the character is not
	echoed on the screen. The meaning of the returned ch depends on
	type returned.
.RETURNS EOF / NOK / _STANDARD_ / _ARROW_ / _KEYPAD_ / _PF_ / _FK_
\begin{TeX}
\begin{itemize}
\item (NOK)	if an error occured, or a very long time-out period expired.
\item (\_EOF\_) if input file terminated (in batch mode);
		ch contains the control char (0 in Batch)
\item (\_STANDARD\_) for a normal (ASCII printable or control character)
\item (\_ARROW\_) for arrows; ch contains the following:
	\begin{itemize}
	\item (\_UP\_) up arrow key
	\item (\_DOWN\_) down arrow key
	\item (\_RIGHT\_) right arrow key
	\item (\_LEFT\_) left arrow key
	\end{itemize}
	Note that the return is also \_ARROW\_ if some control characters
		were mapped to cursor movements (see tv\_open)
\item (\_KEYPAD\_) for a character from the keypad; ch contains `0' \dots `9' 
	\quad `-'  \quad `.' \quad  `,' \quad `\b r' (Enter)
\item (\_PF\_) for Programmable Function keys: ch contains 
	1 for PF1, 2 for PF2, etc\dots
\item (\_FK\_) for other function keys: ch contains 1 for FK1, etc\dots
\end{itemize}
\end{TeX}
.REMARKS The cursor position remains unchanged.
	The default char (no input) is null.
------------------------*/
	char	*ch;		/* OUT: Character typed at keyboard */
{
	register int index, llow;
	register char *p;
	int	 type, seq;

  ENTER("tv_getc");

#if DEBUG
  TRACE_ED_STR2("In Buffer: ", BUFR + bufp, buftop-bufp);
#endif

  if (terms->last_type)			/* Something already exists */
  {	*ch  = terms->last_char;
  	type = terms->last_type;
  	FINISH;
  }

  if(bufp >= buftop ) 			/* The buffer is empty	*/
  {	bufp = 0, buftop = 0;
	type = tv_in();
	if (type < 0)	FINISH;		/* Nothing more		*/
	buftop += type;
	type = NOK;
	if(buftop == 0)	FINISH;		/* ... still empty!	*/
  }

  *ch  = BUFR[bufp++];
  type= _STANDARD_;

	/* Testing the char; Raw mode does not check the incoming
	 * char. Note that the raw flag is set by the TW_cc_RAW 
	 * input character	*/

  if (RAW_MODE) FINISH;

	/* Look in the Control Char Definitions if the typed char
	 * is mapped to an arrow, the EOF or RAW. If yes, transform type
	 */

  index = *(unsigned char *)ch;
  if (index < 32)
  {	switch(terms->tc[index])
  	{ case TW_cc_RET:	*ch = '\r'; FINISH;
  	  case TW_cc_EOF:	type = EOF; FINISH;
  	  case TW_cc_UP:   case TW_cc_DOWN: case TW_cc_HOME:
  	  case TW_cc_LEFT: case TW_cc_RIGHT: 
		type = _ARROW_;
		*ch = terms->tc[index] - TW_cc_UP;
		FINISH;
	}

	/* Test if the read char can be the starting byte 
	 * of an input sequence: corresponding bit is set to 1		*/

	if (!(terms->specials & MASK(index))) 	FINISH;
  }
  else	FINISH;


	/* Recognition of an escape sequence: a complete matching
	 * occurs when lower and higher limits of partial match
	 * become identical. Remember that the escape sequence is
	 * coded as (length) (2 chars cap-code) `=' (sequence)		*/

  low   = (short int *)(CAPLIST + terms->indexr);
  high  = low + (terms->nsr-1);
  index = 0;

  for (seq=0; ;seq++)
  { 	llow = tv_gaj(ch,index);
	index++;
	if(llow < index)
	{ 	/* The sequence cannot be understood ...	*/
		p = *low + 4 + CAPLIST;
 		index--;
		oscopy(BUFS, p, index);
		*(BUFS + index++) = *ch;
		ERR_ED_STR2("I can't understand sequence:", BUFS, index);
		EXIT(NOK);
	}

	if ((high == low) && (index == llow)) break;

	/* 	Read Next character - if nothing after some delay,
		assume not an escape sequence 			*/

	if (bufp >= buftop ) 
	{	bufp = 0, buftop = 0;
		type = tv_inw(2);
		if (type < 0)	FINISH;
		buftop += type;
		if (seq == 0)	type = _STANDARD_;
		if (buftop ==  0) 		/* still empty... */
			FINISH;
	}
	*ch = BUFR[bufp++];
  }

	/* The sequence was recognized - Set the correct char 
	 * and correct type					*/

  p = *low + CAPLIST;	/* Address of escape sequence	*/

  *ch = *(p+2);		/* 2nd char of capability	*/

  switch (*(p+1))	/* First char of capability	*/

  { case 'k':		/* PF or arrow	keys	*/
	type = _ARROW_;
	switch(*ch)
	{ case 'u': *ch = _UP_; 	break;
	  case 'd': *ch = _DOWN_; 	break;
	  case 'b':		/* in some termcapfiles... */
	  case 'l': *ch = _LEFT_; 	break;
	  case 'r': *ch = _RIGHT_; 	break;
	  case 'h': *ch = _HOME_;	break;	/* home */
	  default : type = _PF_;
		if (isdigit(*ch)) 	*ch -= '0';
		  else			*ch -= 'A'-10;
	}
	break;
    
    case 'K': 		/* Keypad keys		*/
	type = _KEYPAD_;	
	if (*ch == 'E') 	*ch = '\r';	/* ENTER key	*/
	break;
    case 'F':		/* Other function keys	*/
	type = _FK_;	
	if (isdigit(*ch)) 	*ch -= '0';
	   else			*ch -= 'A'-10;
	break;
  }

  FIN:
  terms->last_char = 0;
  terms->last_type = 0;
  EXIT (type); 
}

/*==========================================================================*/
int tv_getb(buf, lbuf)
/*+++++++++++++
.PURPOSE Raw input mode. Up to lbuf bytes are read, but at least 1.
.RETURNS Number of bytes
.REMARKS 
---------------*/
	char	*buf;		/* OUT: What was typed at the terminal	*/
	int	lbuf;		/* IN: Max number of bytes	*/
{
	char 	*p, *pe, *pc;
	int	i;
	char	tc[2];
	MID_RSTATIC	char arrows[] = {'u', 'd', 'l', 'r', 'h'};

  ENTER("tv_getb");

  p = buf, pe = p + lbuf;

  if (terms->last_type)			/* Something already exists */
  {	pc = NULL_PTR(char);
  	tc[1] = terms->last_char;
	switch(terms->last_type)
	{ case _ARROW_:
		tc[1] = arrows[tc[1]];
	  case _PF_:
		tc[0] = 'k';
		break;
	  case _KEYPAD_:
		tc[0] = 'K';
		break;
	  case _FK_:
		tc[0] = 'F';
		tc[1] += (tc[1] > 9 ? 'A'-10 : '0');
		break;
	  default:
	  	pc = &(terms->last_char);
	}
	if (pc)	*(p++) = *pc;
	else	if (pc = SearchCap(tc)) p += oscopy(p, pc+4, *pc);
  	if(bufp >= buftop ) 		/* The buffer is empty	*/
  		FINISH;
  }

  if(bufp >= buftop ) 			/* The buffer is empty	*/
  {	bufp = 0, buftop = 0;
	if ((i = tv_in()) < 0)	FINISH;	/* Nothing more		*/
	buftop += i;
	if(buftop == 0)	FINISH;		/* ... still empty!	*/
  }

  i = MIN(pe-p, buftop - bufp);
  p += oscopy(p, &(BUFR[bufp]), i);
  bufp += i;

  FIN:
  terms->last_char = 0;
  terms->last_type = 0;
  EXIT (p - buf); 
}

/*==========================================================================
 *			tv_inc
 *==========================================================================*/
int tv_inc()
/*+++++++++++++
.PURPOSE Check if input characters are waiting.
.RETURNS Number of input chars waiting, 0 if none.
---------------*/
{
	register int n;

  ENTER("+tv_inc");
  
  n = buftop - bufp;
  if (n <= 0)	n = ostin();
  
  EXIT(n);
}
  
/*==========================================================================
 *			tv_push
 *==========================================================================*/
int tv_push(type, ch)
/*+++++++++++++
.PURPOSE Supply (will be the next input) the read character.
.RETURNS OK 
.REMARKS 
---------------*/
	int  type;	/* IN: Class of character	*/
	char ch;	/* IN: Character in class 	*/
{
  terms->last_char = ch;
  terms->last_type = type;

  return(OK);
}

/*==========================================================================
 *			tv_supply
 *==========================================================================*/
int tv_supply(str, len, queue)
/*+++++++++++++
.PURPOSE Supply (will be the next input) or queue a response, 
	as if it were read from the terminal.
	May be used e.g. in the function used while waiting for input
	(cf tv_fw).
.RETURNS OK / NOK (string too long, truncated)
.REMARKS A length of zero indicates an EOS-terminated string,
	and terminate the string with the carriage-return.
---------------*/
	char *str;	/* IN: String to supply as if read from terminal */
	int  len;	/* IN: Length of String (0 for standard string) */
	int  queue;	/* IN: 0 for top, 1 for queue			*/
{
	register int l;
	char *p;

  ENTER("tv_supply");
  
  if (len <= 0)	
  	len = strlen(str), p = str + len++, *p = '\r';
  else	p = NULL_PTR(char);

  TRACE_STR2(str, len);

	/* Suppress what's already read... 	*/

  if (bufp)	buftop = oscopy(BUFR, BUFR+bufp, buftop - bufp),
  		bufp = 0;

	/* If it's a queue, be sure that all type ahead buffer is
	 * exhausted */

  if (queue)	buftop += tv_in();

  l = terms->buf_size - buftop;		/* Remaining bytes */

  if (len > l)	status = NOK, ERROR("Input truncated");
  else 	 	status = OK , l = len;

  if (queue)	oscopy(BUFR+buftop, str, l);
  else		oscopy(BUFR+l, BUFR, buftop),
  		oscopy(BUFR, str, l);
  buftop += l;
  
  if (p)	*p = EOS;
  
  EXIT(status);
}
  
/*==========================================================================
 *			tv_fw
 *==========================================================================*/
int tv_fw (f)
/*+++
.PURPOSE Specify the function to execute while the terminal is
	waiting for input from keyboard.
.RETURNS OK 
.REMARKS This entry acts as a declaration.
	The function f has one parameter, the remaining count in s before
	the time-out, and should return the modified time-out.
---*/
	int	(*f)();	/* IN: Function to execute (NULL_FCT to do nothing) */
{
  ENTER("tv_fw");
  TRACE_ED_I("Function: ", (long)f);
  terms->fct = f;
  EXIT(OK);
}

