#ifndef lint
static char *rcs_webster_c = "$Header: /mit/sipb/src/src/webster/src/ttyclient/RCS/webster.c,v 1.15 1995/03/01 10:07:03 svalente Exp $";
#endif

/*
 * webster - look words up in the dictionary
 *
 * This program connects to the Webster server to get definitions of words.
 * Words may be given on the command line, or, if no arguments are given,
 * the program runs interactively.  If -s or -d is given, it puts the program
 * in "spell" or "define" mode, respectively.  "define" is the default.
 *
 * In either mode, a word may include the wildcard characters '%' and '*'.
 * The '%' character matches exactly one character, while the '*' matches
 * zero or more characters.  If wildcards are used, the program will
 * return either "No match" or a list of matching words.  "!word" looks the
 * word up in the alternate mode.
 *
 * In interactive mode only, Tenex-style command completion may also be
 * used.  Typing a '?' following part of a word will cause the program
 * to print all words which begin with the partial word, or the program
 * will beep if nothing matches.  Typing an ESCape character causes the
 * program to attempt to complete the word.  If the word can be completed,
 * the new word is printed; otherwise, the program beeps.  Wildcards
 * may be used to specify the partial words.
 *
 * David A. Curry
 * Purdue University
 * Engineering Computer Network
 * April, 1986
 *
 * $Log: webster.c,v $
 * Revision 1.15  1995/03/01  10:07:03  svalente
 * Ansi-ized the #else's and #endif's.
 *
 * Revision 1.14  1994/07/06  01:30:51  svalente
 * Added reset_terminal().  (It was previously a ttyclient specific routine
 * in a library file.)
 *
 * Revision 1.13  93/07/25  01:55:28  svalente
 * Fixed what I screwed up last time.  It works on Solaris now.
 * 
 * Revision 1.12  93/07/17  00:19:06  svalente
 * Added a bit of Posix compatibility for Linux and Solaris.
 * 
 * Revision 1.11  1993/07/15  03:55:26  ckclark
 * <strings.h> must die!
 *
 * Revision 1.10  1991/10/22  22:51:57  probe
 * Needs <sys/select.h> on AIX
 *
 * Revision 1.9  91/09/24  08:43:40  eichin
 * don't declare malloc on next - header files are correct
 * 
 * Revision 1.8  89/04/09  01:24:45  jik
 * Added #include <strings.h> -- compiler was complaining that rindex is 
 * an int being assigned to a char *.  Confused compiler.
 * 
 * Revision 1.7  88/09/09  04:53:02  jik
 * Fixed to handle multiple hesiod entries and to check each one in turn
 * before giving up.
 * 
 * Revision 1.6  88/03/01  05:03:55  ambar
 * fixed handling of -DDEBUG flag.
 * 
 * Revision 1.5  88/02/27  03:55:32  ambar
 * fixes to make completion, &etc, work properly with SUNRPC;
 * added ifdef's to find the server through HESIOD.
 * 
 * Revision 1.4  88/02/27  00:25:57  ambar
 * ifdef'd for SUNRPC
 * 
 * Revision 1.3  86/12/26  17:45:20  davy
 * Updated header comments.
 * 
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <signal.h>
#include <netdb.h>
#include <ctype.h>
#include <stdio.h>
#include <webster.h>
#include <string.h>
#include <stdlib.h>

#ifdef _AIX
#include <sys/select.h>
#endif

#ifdef SUNRPC
#include <rpc/rpc.h>
#include <sunrpcweb.h>
#else
#include <netinet/in.h>
#endif

#ifdef HESIOD
#include <hesiod.h>
#endif

#define COMPLETE	033		/* word completion character ^[ */
#define ENDINGS		'?'		/* print matches character      */
#define ALTMODE		'!'		/* use other lookup mode	*/
#define DEFINE		0		/* DEFINE mode			*/
#define SPELL		1		/* SPELL mode			*/

#ifndef POSIX
#include <sgtty.h>
struct sgttyb sgttyb;			/* tty modes when interactive	*/
struct sgttyb rsgttyb;			/* original tty modes		*/
struct tchars tchars;			/* for getting eof char		*/
struct ltchars ltchars;			/* for getting word/line erase	*/
#define signal_type int
#else
#include <termios.h>
struct termios tty;                     /* tty modes when interactive   */
struct termios rtty;                    /* original tty modes           */
#define signal_type void
#endif

extern signal_type byebye();
extern signal_type suspend();

char endoffile;
char backspace;
char worderase;
char lineerase;
char linertype;
char *whoami;
char **hostlist;

int mode = DEFINE;
int interactive = 0;			/* 1 when running interactive	*/
#ifndef SUNRPC
extern FILE *WebsterSock;		/* for reading from the server	*/
#endif

/*
 * we assume the server is
 * running on the same machine
 * as the client in DEBUG mode.
 */
#ifdef SUNRPC
#ifdef DEBUG
#include <sys/param.h>
char servername[MAXHOSTNAMELEN];
#endif
#endif

main(argc, argv)
int argc;
char **argv;
{
     int didone = 0;
#ifdef HESIOD
     char **hes_resolve();
#endif

     if ((whoami = strrchr(argv[0], '/')) == (char *) NULL)
	  whoami = argv[0];
     else
	  whoami++;
	
#ifdef HESIOD
     /* who are we supposed to talk to? */
     if ((hostlist = hes_resolve("webster", "sloc")) == (char **) NULL) {
	  /* grab the first server and make it the one we talk to. */
	  hostlist = (char **) malloc(sizeof(char *) * 2);
	  hostlist[0] = WEBSTERHOST;
	  hostlist[1] = (char *) NULL;
     }
#else
     hostlist = (char **) malloc(sizeof(char *) * 2);
     hostlist[0] = WEBSTERHOST;
     hostlist[1] = (char *) NULL;
#endif

#ifndef SUNRPC
     /*
      * Connect to the server.
      */
     connectup();
#else
#ifdef DEBUG

     if (gethostname(servername, MAXHOSTNAMELEN)) {
	  fprintf(stderr, "Don't know what host name this is?!?!?");
	  exit(-1);
     }
     hostlist[0] = servername;
     hostlist[1] = (char *) NULL;
#endif
#endif
     /*
      * If we were given command line arguments, just
      * try to define each word.
      */
     if (argc > 1) {
	  while (--argc) {
	       if (**++argv == '-') {
		    switch (*++*argv) {
		    case 'd':
			 mode = DEFINE;
			 continue;
		    case 's':
			 mode = SPELL;
			 continue;
		    default:
			 fprintf(stderr, "Usage: webster [-d] [-s] [words...]\n");
			 exit(1);
		    }
	       }

	       /*
		* Look up the word.
		*/
	       if (mode == DEFINE)
		    define(*argv);
	       else
		    spell(*argv);

	       didone++;
	  }

	  if (didone)
	       exit(0);
     }

     /*
      * If no arguments were given, set up the
      * terminal modes and run interactively.
      */
     setup();
     interact();
}

/*
 * setup - turns on CBREAK, turns off ECHO.  This is where we find out the
 *	   special terminal characters (erase, etc.).  Also trap signals.
 */
setup()
{
	interactive = 1;

#ifndef POSIX
	ioctl(0, TIOCGETP, &sgttyb);
	ioctl(0, TIOCGETC, &tchars);
	ioctl(0, TIOCGLTC, &ltchars);

	rsgttyb = sgttyb;
	endoffile = tchars.t_eofc;
	lineerase = sgttyb.sg_kill;
	backspace = sgttyb.sg_erase;
	worderase = ltchars.t_werasc;
	linertype = ltchars.t_rprntc;
#else
	(void) tcgetattr(0, &tty);

	rtty = tty;
	endoffile = tty.c_cc[VEOF];
	lineerase = tty.c_cc[VKILL];
	backspace = tty.c_cc[VERASE];
	worderase = tty.c_cc[VWERASE];
	linertype = tty.c_cc[VREPRINT];
#endif
	signal(SIGINT, byebye);
	signal(SIGQUIT, byebye);
	signal(SIGTSTP, suspend);

#ifndef POSIX
	sgttyb.sg_flags |= CBREAK;
	sgttyb.sg_flags &= ~ECHO;
	ioctl(0, TIOCSETP, &sgttyb);
#else
        tty.c_lflag &= ~(ICANON|ECHO);
	tty.c_cc[VTIME] = 0;
	tty.c_cc[VMIN] = 1;
	(void) tcsetattr(0, TCSANOW, &tty);
#endif
}

/*
 * reset_terminal - put the terminal back to normal.  undo the damage
 *	done by setup().
 */
void reset_terminal ()
{
#ifndef POSIX
	ioctl(0, TIOCSETP, (char *) & rsgttyb);
#else
	tcsetattr(0, TCSANOW, &rtty);
#endif
}

/*
 * interact - interact with the user.
 */
interact()
{
	char c;
	char buf[BUFSIZ];
	register char *s, *t;

	/*
	 * Forever...
	 */
	for (;;) {
		/*
		 * Prompt for a word.
		 */
		s = buf;
		write(1, "Word: ", 6);

		/*
		 * Forever... read characters.  We
		 * break out of this from inside.
		 */
		for (;;) {
			if (read(0, &c, 1) <= 0)
				byebye();

			if ((int)c == endoffile) {
				/*
				 * If at the start of a line,
				 * treat it as end of file.
				 * Otherwise, ignore it.
				 */
				if (s == buf)
					byebye();
				continue;
			}
			else if ((int)c == backspace) {
				/*
				 * If not at the beginning of a line,
				 * back up one character.
				 */
				if (s > buf) {
					write(1, "\b \b", 3);
					s--;
				}
				continue;
			}
			else if ((int)c == worderase) {
				/*
				 * Until we hit beginning of line
				 * or beginning of word, back up.
				 */
				while ((s > buf) && (*s != ' ')) {
					write(1, "\b \b", 3);
					s--;
				}
				continue;
			}
			else if ((int)c == lineerase) {
				/*
				 * Until we hit beginning of line,
				 * back up.
				 */
				while (s > buf) {
					write(1, "\b \b", 3);
					s--;
				}
				continue;
			}
			else if ((int)c == linertype) {
				/*
				 * Retype the line.
				 */
				write(1, "\r\nWord: ", 8);

				for (t = buf; t < s; t++)
					write(1, t, 1);
				continue;
			}
			else if ((int)c == COMPLETE) {
				/*
				 * Try to complete what they typed
				 * so far.  Put the pointer at the
				 * end of the new word.
				 */
				*s = '\0';
				complete(buf);
				for (s = buf; *s; s++)
					;
				continue;
			}
			else if ((int)c == ENDINGS) {
				/*
				 * If it's the first character,
				 * then print some help.  Otherwise,
				 * try to find endings for the word.
				 * endings() returns 1 if no endings
				 * were found, 0 if some were found.
				 * This tells us whether to reprint
				 * the current word or not.
				 */
				if (s == buf) {
					help();
				}
				else {
					*s = '\0';
					if (endings(buf) == 0) {
						write(1, "Word: ", 6);

						for (s = buf; *s; s++)
							write(1, s, 1);
					}

					continue;
				}
			}
			else if ((int)c == '\n') {
				/*
				 * If at the start of a word,
				 * newline is exit.
				 */
				if (s == buf)
					byebye();

				/*
				 * Otherwise, try to define
				 * the word.
				 */
				*s = '\0';
				write(1, "\n", 1);

				if (*buf == ALTMODE) {
					if (strlen(buf) == 1)
						break;
					if (mode == DEFINE)
						spell(buf+1);
					else
						define(buf+1);
				}
				else {
					if (mode == DEFINE)
						define(buf);
					else
						spell(buf);
				}
			}
			else {
				/*
				 * Echo the character and copy it.
				 */
				write(1, &c, 1);
				*s++ = c;
				continue;
			}

			break;
		}
	}
}

/*
 * help - print a help message.
 */
help()
{
	printf("\n   Type in the word you want defined, or a blank line to exit. Additionally,\n");
	printf("Webster can match words using wildcards.  The character '%%' in a word means\n");
	printf("match exactly one character; while the character '*' means match zero or more\n");
	printf("characters.  Typing \"!word\" will check the spelling of the word instead\n");
	printf("of checking its definition.\n");
	printf("   Typing a partial word followed by '?' will print all the words in the\n");
	printf("dictionary which match your partial word. Typing a partial word followed by an\n");
	printf("ESCape character will try to complete the word for you.  If the partial word\n");
	printf("is ambiguous, Webster will beep at you.  Note that you can use the wildcards\n");
	printf("along with ESC and ?.  For example (the underlined parts are typed by the\n");
	printf("user, the rest by Webster),\n");
	printf("\n");
	printf("Word: balla?   Maybe you mean:\n");
	printf("      ------\n");
	printf("  1. ballad           2. ballade          3. baladry         4. ballast\n");
	printf("Word: pluria<ESC>xial\n");
	printf("      -----------\n");
	printf("Word: plu*x<ESC>\n");
	printf("      ----------\n");
	printf("Word: pluriaxial\n");
	printf("\n---- End of Help File ----\n\n");
}

/*
 * suspend - reset tty modes and supend ourselves.
 */
signal_type suspend()
{
#ifndef POSIX
	long blocked;

	/*
	 * Reset tty modes and suspend.
	 */
	ioctl(0, TIOCSETP, &rsgttyb);
	signal(SIGTSTP, SIG_DFL);
	blocked = sigsetmask(0);
	kill(0, SIGTSTP);

	/*
	 * We come here on SIGCONT.  Reset
	 * the signal mask and tty modes.
	 */
	sigsetmask(blocked);
	signal(SIGTSTP, suspend);
	ioctl(0, TIOCSETP, &sgttyb);	
#else
	sigset_t blocked, empty;

	/*
	 * Reset tty modes and suspend.
	 */
	(void) tcsetattr(0, TCSANOW, &rtty);
	signal(SIGTSTP, SIG_DFL);
	sigemptyset (&empty);
	sigprocmask (SIG_SETMASK, &empty, &blocked);
	kill(0, SIGTSTP);

	/*
	 * We come here on SIGCONT.  Reset
	 * the signal mask and tty modes.
	 */
	sigprocmask(SIG_SETMASK, &blocked, NULL);
	signal(SIGTSTP, suspend);
	(void) tcsetattr(0, TCSANOW, &tty);
#endif
}
