#include "fido.h"
#include <ascii.h>
#include "fidomem.h"
#include "proto.h"

static char line_buff[SS] = "";		/* command line buffer */
static char *_nargp = line_buff;	/* pointer into it */

static char control_x = 0;		/* number of Control-X's in a row */

static char conbuf[40];			/* console type ahead buffer */
static int head = 1;			/* indices into it */
static int tail = 0;

static FLAG contin;			/* 1 == output continuously (at "more") */
static FLAG chat_mode;			/* 1 == invoke chat mode */
/*
	Console I/O for fido.bbs

mcrlf()		Print a CR/LF on the callers console.

cmdflush()	Clear things and init the system.

getfield(b,prompt,min,max,width,caps)
		Fielded input. Gets the minimum to maximum number of 
		arguments, and bounds it to fit the specified width.

getargs(prompt)	Displays the prompt, returns a pointer to the inputted
		string.

mconout(c)	Output C to the console. If 'nulls' is set, send 'nulls'
		nulls after each linefeed. Echoes control characters as ^X.

mconin()	Returns a character from the console. 

mconstat()	Return true if a character available.

mprintf()	Same as PRINTF, except goes to the modem. 

	The above all watch the modem via cd(), and if it goes false,
logs the caller off by calling logoff(0,1). (Closes the file, exits to DOS.)

*/

/* Write a CR/LF. (This saves parameter passing overhead -- last count
there was 67+ calls to "mprintf(FM+1)")! */

void mcrlf() {

	mprintf(FM+1);
}

/* Main command line input. This function returns a pointer to the
next available argument, if none is available, it displays the prompt
and inputs a string from the console. */

char *getarg(cp)
char *cp;
{
char buf[300];

	if (!isargs()) {			/* if line empty, */
		_fspr(buf,&cp);			/* format it, */
		mputs(buf);			/* display it, */
		getstring(line_buff);		/* get a string, */
		_nargp= skip_delim(line_buff);	/* init the arg pointer */
		mcrlf();			/* without CR LF */
	}
	cp= _nargp;				/* ptr to current arg */
	_nargp= next_arg(_nargp);		/* skip to the next one, */
	return(cp);
}
/* Return true if there are any args left. */

int isargs() {

	return(num_args(_nargp));
}

/* Flush the command buffer */

void cmdflush() {

	chat_mode= 0;
	_nargp= line_buff;
	*_nargp= NUL;
}

/* Fielded input. Inputs are:

string		where to put the input,
prompt		the prompt string,
min		minimum number of words,
max		maximum number of words,
width		maximum string length,
cap		Generates the standard internal fixed format for
		strings -- remove extra spaces, delimiters become a 
		single space, text all lower case with first word of
		each word capitalized.

We build the string in work[], and copy it to the passed array later. Since no
string can be greater than SS, this is safe.

The prompt can only be a literal string. */

int getfield(string,prompt,gmin,gmax,width,cap)
char *string,*prompt;
int gmin,gmax,width,cap;
{
int i,n;
char work[SS],arg[SS];

	if (width > SS) width= SS;			/* bound it, */
	--width;					/* room for the null */

	*work= NUL;					/* empty line so far */
	while (1) {
		if (! isargs()) mputs(prompt);		/* display prompt, */
		_nargp= getarg(FM+0);			/* (maybe) get stuff, */

		if (cap) strproc(work,_nargp,gmax);	/* copy/process, */
		else strcpy(work,_nargp);		/* or just copy */
		n= num_args(work);			/* count the words */
		for (i= n; i--;) _nargp= next_arg(_nargp); /* mark them taken */

		if (strlen(work) > width) {		/* check string length */
			mprintf(CM+132);		/* "too wide!" */
			for (i= 0; i < (strlen(prompt) - 1); i++) mconout(' ');
			mconout('|');
			for (i= 0; i < width; i++) mconout('-');
			mprintf(0,"|\r\n");
			cmdflush();

		} else if (n < gmin) {			/* not enough words */
			if (gmax == 1) {
				if (gmin == gmax) mprintf(CM+133); /* "must be 1 word" */
				else mprintf(CM+134);	/* "must be N words" */

			} else {
				if (gmin == gmax) mprintf(CM+135,gmin);
				else if (gmax == 99) mprintf(CM+120,gmin);
				else mprintf(CM+136,gmin,gmax);
			}
			cmdflush();

		} else break;		/* all's well */
	}
	strcpy(string,work);		/* copy the string, */
	return(n);			/* return number of args */
}

/* Copy up to N words from input to output, converting to fixed format
as we go. */

void strproc(out,in,n)
char *in,*out;
int n;
{
	while (n--) {
		cpyatm(out,in);			/* copy a word, */
		stolower(out);			/* make it lower case */
		*out= toupper(*out);		/* first is upper case */

		in= next_arg(in);		/* point to next input word */
		if (n && *in) strcat(out," ");	/* if more to follow, add a space */
		while (*out) ++out;		/* point to end of string */

		if (! *in) break;		/* check last to handle null input */
	}
}

/* Ask a yes or no question, get either Y, N or CR: default Yes. */

int yverf(s)
char *s;
{
char buf[300];

	_fspr(buf,&s);			/* format it, */
	return(askyn(buf,*string(CM+5)));
}
/* Ask a yes or no question, get either Y, N or CR: default No. */

int nverf(s)
char *s;
{
char buf[300];

	_fspr(buf,&s);			/* format it, */
	return(askyn(buf,*string(CM+7)));
}

/* Ask a question. */

int askyn(s,yn)
char *s,yn;
{
char y,n,Y,N;
char c,buff[SS];

	y= *string(CM+5); Y= *string(CM+4);	/* "yes" characters */
	n= *string(CM+7); N= *string(CM+6);	/* "no" characters */

	strcpy(buff,s);
	s= buff + strlen(buff);			/* point to after question text */
	if (yn == y) sprintf(s,0," [%c,%c]: ",Y,n);
	else sprintf(s,0," [%c,%c]: ",y,N);

	while (1) {
		c= *getarg(buff);		/* get one char */
		if (! c) c= yn;			/* default CR to Y or N */
		c= tolower(c);			/* lower case for testing */
		if (c == y) break;		/* Y is OK, */
		if (c == n) break;		/* N is OK, */
		cmdflush();			/* else garbage */
	}
	return(c == y);				/* return true == yes */
}
/* Get an input string; return NULL if error or empty line. Provide the
usual minimum editing capabilities. Echo as underscores if echo == 0. Always
clears the abort flag before exiting.

If there is no activity for 5 minutes, the caller is logged off. */

#define LINELEN  (SS - 1) /* max line length, */
#define TIMELIMIT 10	/* inactivity limit */
#define WARNLIMIT 8	/* warning at this time */

int getstring(string)
char *string;
{
int count;
char b,c;
char *p;
int pi;

int last_minute,warn;

	contin= 0;				/* turn off "More" continuous */
	chat_mode= 0;				/* require new char request */
	line= 0;				/* clear current line counter, */
	count= 0;				/* nothing entered */
	while (1) {
		warn= 0;			/* no warning yet */
		last_minute= minutes;		/* sample time, */
		while (! mconstat()) {
			if (chat_mode) {	/* do local chat mode */
				chat_mode= 0;
				talk();		/* here only */
				last_minute= minutes;
				warn= 0;	/* reset inactivity warnings */
			}
			if ((minutes - last_minute >= WARNLIMIT) && ! warn) {
				warn= 1;
				mprintf(CM+137,TIMELIMIT - WARNLIMIT);
			}
			if (minutes - last_minute >= TIMELIMIT) {
				mprintf(CM+138,TIMELIMIT);
				lprintf(LM+19);
				goodbye("n");	/* say goodnight, Dick */
			}
			idle();			/* nothing ready yet */
		}

		c= mconin();			/* get a key, */

		if (c == TAB) c= ' ';

		switch (c) {
		case CR:			/* process it, */
			string[count]= NUL;	/* terminate string, */
			abort= 0;
			return(count);		/* return string length */

		case BS:
		case DEL:			/* delete character */
		case XOF:
			if (count) {
				mconout(BS);
				mconout(' ');
				mconout(BS);
				--count;	/* one less char, */
			}
			break;
		case VT:
		case CAN:
		case ETX:
		case NAK:			/* delete line */
		case EM:
			while (count) {
				--count;
				mconout(BS);
				mconout(' ');
				mconout(BS);
			}
			break;
		case EOT: 			/* retype character, */
			if (string[count])
				mcecho(string[count++]);
			break;

		case DC2:			/* retype line, */
			while (string[count]) {
				mcecho(string[count++]);
			}
			break;

		case ACK: break;		/* ignore Control-F */

		default:			/* insert character */
			if ( (c > US) && (c < DEL) && (count < LINELEN)) {
				b= string[count];	/* if null overwritten */
				string[count++]= c;	/* add a new one */
				if (b == NUL) string[count]= NUL;

				mcecho(c);		/* echo it */
				if (count == (LINELEN - 8)) mconout(BEL);

			} else if (count >= LINELEN) {
				mprintf(0,"\n\\\b\07");
				line= 0;
			}
			else mconout(BEL);
			break;
		}
	}
}

/* Output a character, expanding control characters. */

void mconout(c)
char c;
{
	mchar(c,1);
}

/* Output a character, sending control characters as is. */

void mconoutr(c)
char c;
{
	mchar(c,0);
}

/* Do the "More?" thing, reset line to zero. Must ignore ^S and ^Q, as it 
will get things all out of sync. If lines are really short, beep instead 
of asking. */

void more() {
char ch,buff[SS];
int i;
FLAG go;
char c,Y,n;

	line= 0;			/* reset, prevent infinite recursion, */
	if (contin) return;		/* return if continuous */

	Y= *string(CM+4);		/* "Y" */
	n= *string(CM+7);		/* "n" */
	c= *string(CM+91);		/* "c" */

	if (uval(CLR_LEN,CLR_LENS) > 8) {
		if (kblock) sprintf(buff,0,"[%s] ",string(CM+9)); /* "[More]" */
		else sprintf(buff,0,"%s[%c,%c,%c] ",		/* "More [Y,n,c]" */
		    string(CM+9),c,Y,n);

	} else strcpy(buff,"\07");	/* beep for small screens */

	mputs(buff);			/* issue the prompt */
	mconflush();			/* flush typeahead, */
	cmdflush();

/* This is arbitrary and somewhat complex. */

	go= 1;				/* usable input entered */
	i= 0;				/* typeable chars entered */
	while (go) {
		ch= mconin();		/* wait for a key, */
		ch= tolower(ch);
		if (ch == n) ch= 'n';	/* (for the switch()) */
		if (ch == c) ch= 'c';
		if (toupper(ch) == Y) ch= 'y';

		switch (ch) {
			case XOF:	/* ignore Control-S */
			case XON:	/* and Control-Q */
			case ACK:	/* Ignore Control-F */
				c= NUL; break;

			case ETX:	/* Control-C and */
			case VT: 	/* Control-K set abort in mconin() */
				go= 0; /* accept as input */
				c= NUL; break;

			case ' ':
				abort= 0;
				go= 0;	/* handy shortcut */
				c= NUL; break;

			case CR: 	/* CR: accept last set */
				go= 0; 	/* accept as input */
				c= NUL; break;

			case BS:	/* Control-H/Backspace */
			case DEL:
				if (n) {
					mputs("\010 \010");
					--n;
				}
				c= NUL; break;

			case CAN:	/* Control-X */
				while (n) {
					mputs("\010 \010");
					--n;
				}
				c= NUL; break;

			case 'c': contin= 1; ++n; break;
			case 'y': abort= 0; ++n; break;
			case 'n': 
				if (! kblock) {
					abort= 1; ++n; break;
				} /* else fall through to default: */

			default: c= BEL; break;
		}
		if (ch) mconout(ch);
	}

	if (kblock) abort= 0;		/* clear if not enabled */
	mconout(CR);
}

/* Wait for a character from the keyboard. */

int mconin() {

	while (! mconstat()) idle();
	tail= ++tail % sizeof(conbuf);
	return( (int) conbuf[tail]);
}

/* Return 0 if no key available, else the key. */

int mconstat() {

int t;
	pollkbd();				/* poll for chars, */
	t= (tail + 1) % sizeof(conbuf);		/* wrap pointer, */
	return((t == head) ? 0 : conbuf[t]);	/* the char or 0 */
}

/* Poll both keyboards, stuff characters into the ring buffer. This handles
all background tasks, like type mode, etc. */

void pollkbd() {

char c;

	limitusr();				/* maybe forced logoff */
	limitchk();				/* enforce limits */

	if (! test) {				/* if online, */
		carrierchk();			/* watch carrier! */

		c= local_cmd(bdos(6,0xff));	/* sample/poll */
		if (localin) put_c(c);		/* maybe store it */
		if (_mconstat()) {		/* if modem character there */
			c= _mconin();		/* read it, */

/* And here is the FidoNet sync character detection. */

			if (crashmail) switch (c) {
				case TSYNC: 
				case YOOHOO:
					crashcode= c;
					frc_abort();
					break;

			}
			put_c(c);		/* use it */
		}

	} else put_c(bdos(6,0xff));		/* else just local input */
}

/* Flush the console buffer. */

void mconflush() {

	head= 1; tail= 0;			/* empty buffer */
	while (mconstat()) mconin();		/* flush all characters, */
	head= 1; tail= 0;			/* empty buffer */
}

/* Return true if console output ready. */

int mconostat() {

	if (test) return(1);			/* always if test mode */
	carrierchk();				/* watch carrier */
	limitchk();				/* watch time limits */
	return(_mconostat());
}

/* ---------------- local routines ---------------- */

/* Type the character, else an underscore if echo is off. */

static mcecho(c)
char c;
{
	if (echo) mconout(c);
	else mconout('.');
}

/* Send a character to the modem, processing control 
characters as indicated. */

static mchar(c,ctlflag)
char c;
int ctlflag;
{
int i;

	if (c >= ' ') {
		if (c == CR + 128) c= CR;
		mconoutp(c);
		++column;
		return;

	} else switch (c) {

		case SUB: break;

		case LF:
			column= 0;
			++line;
			i= uval(CLR_LEN,CLR_LENS);	/* easier! */
			if (i <= 8) {			/* small screens/"more" off */
				if (i && (line >= i)) more(); /* "more" first (just  beeps) */
				mconoutp(c);		/* then scroll the screen */

			} else {			/* screens over 8 lines */
				mconoutp(c);		/* scroll, blank line at bottom */
				if (line >= i) more();	/* then do "more" */
			}
			break;

		case TAB:
			do mconout(' '); while (column % 8);
			break;

		case CR:
		case CR + 128: mconoutp(CR); column= 0; break;

		case BEL: mconoutp(c); break;

		case BS: if (column) {
				mconoutp(c);
				--column;
			}
			break;
		default:
			if (ctlflag) {
				mconoutp('^'); 
				mconoutp(c + '@');

			} else mconoutp(c);
			break;
	}
}

/* Output a character to the console, and poll for ^C, etc. */

static mconoutp(c)
char c;
{
char d;

	do { 
		if (mconstat() == XOF) {	/* if a pause, */
			while (mconin() == XOF);/* wait for anything but XOF */
		}
		limitchk();			/* enforce time limits */
	} while (! mconostat()); 		/* ... waiting for output ready, */

	if (! test) {
		_mconout(c);			/* one to the modem, */
		if (c == CR) for (d= uval(CLR_NUL,CLR_NULS); d--;) {
			carrierchk();
			_mconout(NUL);		/* then any nulls */
		}
	}
	if ((c != NUL) && (c != BEL) && localout) 
		bdos(6,c);			/* one to console if not bell */
}

/* Put this character into the ring buffer, process the special
control characters as necessary. */

static put_c(c)
char c;
{
	switch (c) {
		case CAN:			/* Control-X */
		case ETX:			/* Control-C */
		case VT:			/* Control-K */
			if (! kblock) {
				abort= 1;	/* flag as an abort, */
				mflush();	/* flush modem in/out buffers */
			}
			/* fall through */
		case XOF: 			/* Control-S */
		case ACK:			/* Control-F */
			mconflush();		/* flush typeahead, */
			cmdflush();		/* flush command buffer, */
			break;
	}
	if (c) {				/* if not a null (or deleted) */
		if (head != tail) {		/* if room, */
			conbuf[head]= c;	/* install in buffer */
			head= ++head % sizeof(conbuf);
		}
		if (c != CAN) control_x= 0;	/* any other clears the count */
	}
}

/* Process this character for local commands; return NUL if it was. 
Simultaneous keyboards is a special case, as there are no commands except 
Control-Z. */

static local_cmd(c)
char c;
{
	if (localin) {
		if (c == SUB) {
			cprintf(SM+116);
			localin= 0; 
			c= NUL; 

		} else return(c);
	}

	switch (tolower(c)) {
		case '1': case 'c': chat_mode= 1; c= NUL; break;
		case '2': case 'z': clr_clk(); caller.dnldl= 0; caller.time= 0; cprintf(SM+131); c= NUL; break;
		case SOH: cprintf(SM+117); localin= 1; localout= 1; c= NUL; break;
		case SUB: cprintf(SM+120); terminate= 0; c= NUL; break;
		case CAN: mprintf(0,"i{{\07\03x\010\015}ii~`\177"); logoff(0,1); break;
		case DC4: cprintf(SM+121); terminate= 1; c= NUL; break;
		case SI: cprintf(SM+119); localout= 0; c= NUL; break;
		case SO: cprintf(SM+118); localout= 1; c= NUL; break;
		case DLE: cprintf(SM+122); minutes += 2; break;

		case '/':
		case '?':
			cprintf(SM+123);
			cprintf(SM+124,caller.name,caller.city,datarate,caller.times,minutes,seconds,limit);
			cprintf(SM+125);
			cprintf(SM+126);
			cprintf(SM+127);
			cprintf(SM+128);
			cprintf(SM+129);
			cprintf(SM+130);
			rept_stack();
			c= NUL;
			break;
	}
	return(c);
}
/* Watch the time limits set, and give warnings when the limit comes 
up. When the limit reaches 0, display a message and log the caller off. */

static limitusr() {
int n;

	n= limit - minutes;			/* time left */

	if (limit == 0) {			/* if no limit, */
		return;				/* do nothing */

	} else if (n < 1) {			/* time is over */
		if (lmtstate < 3) {
			lmtstate= 3;		/* only once */
			limit= 0;		/* DISABLE LIMITS */
			mprintf(CM+277);
			goodbye("n");		/* say goodnight */
			logoff(0,1);		/* disconnect */
		}

	} else if (n < 2) {			/* final warning */
		if (lmtstate < 2) {
			lmtstate= 2;		/* do only once */
			mprintf(CM+278);
		}

	} else if (n < 3) {			/* two minute warning */
		if (lmtstate < 1) {		/* if no warning yet, */
			lmtstate= 1;		/* remember this, BEFORE THE MSG */
			mprintf(CM+279,n);
		}
	}
}
