/* getkey.cpp */

/* Synchronet single-key console functions */

/* $Id: getkey.cpp,v 1.37 2006/08/23 01:45:05 rswindell Exp $ */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
 * Copyright 2006 Rob Swindell - http://www.synchro.net/copyright.html		*
 *																			*
 * 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.					*
 * See the GNU General Public License for more details: gpl.txt or			*
 * http://www.fsf.org/copyleft/gpl.html										*
 *																			*
 * Anonymous FTP access to the most recent released source is available at	*
 * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net	*
 *																			*
 * Anonymous CVS access to the development source and modification history	*
 * is available at cvs.synchro.net:/cvsroot/sbbs, example:					*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login			*
 *     (just hit return, no password is necessary)							*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src		*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * You are encouraged to submit any modifications (preferably in Unix diff	*
 * format) via e-mail to mods@synchro.net									*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

#include "sbbs.h"
#include "telnet.h"	// TELNET_GA

/****************************************************************************/
/* Waits for remote or local user to hit a key. Inactivity timer is checked */
/* and hangs up if inactive for 4 minutes. Returns key hit, or uppercase of */
/* key hit if mode&K_UPPER or key out of KEY BUFFER. Does not print key.    */
/* Called from functions all over the place.                                */
/****************************************************************************/
char sbbs_t::getkey(long mode)
{
	char	ch,coldkey,c=0,spin=sbbs_random(5);
	time_t	last_telnet_cmd=0;

	if(!online || !input_thread_running) {
		YIELD();	// just in case someone is looping on getkey() when they shouldn't
		return(0);
	}
	sys_status&=~SS_ABORT;
	if((sys_status&SS_USERON || action==NODE_DFLT) && !(mode&(K_GETSTR|K_NOSPIN)))
		mode|=(useron.misc&SPIN);
	lncntr=0;
	timeout=time(NULL);
	if(mode&K_SPIN)
		outchar(' ');

	do {
		if(sys_status&SS_ABORT) {
			if(mode&K_SPIN) /* back space once if on spinning cursor */
				backspace();
			return(0); 
		}

		if(mode&K_SPIN) {
			if(useron.misc&NO_EXASCII) {
				switch(c++) {
					case 0:
						outchar(BS);
						outchar('|');
						break;
					case 1:
						outchar(BS);
						outchar('/');
						break;
					case 2:
						outchar(BS);
						outchar('-');
						break;
					case 3:
						outchar(BS);
						outchar('\\');
						c=0;
						break;
				}
			} else {
				switch(spin) {
					case 0:
						switch(c++) {
							case 0:
								outchar(BS);
								outchar('³');
								break;
							case 1:
								outchar(BS);
								outchar('/');
								break;
							case 2:
								outchar(BS);
								outchar('Ä');
								break;
							case 3:
								outchar(BS);
								outchar('\\');
								c=0;
								break;
						}
						break;
					case 1:
						switch(c++) {
							case 0:
								outchar(BS);
								outchar('°');
								break;
							case 1:
								outchar(BS);
								outchar('±');
								break;
							case 2:
								outchar(BS);
								outchar('²');
								break;
							case 3:
								outchar(BS);
								outchar('Û');
								break;
							case 4:
								outchar(BS);
								outchar('²');
								break;
							case 5:
								outchar(BS);
								outchar('±');
								c=0;
								break;
						}
						break;
					case 2:
						switch(c++) {
							case 0:
								outchar(BS);
								outchar('-');
								break;
							case 1:
								outchar(BS);
								outchar('=');
								break;
							case 2:
								outchar(BS);
								outchar('ð');
								break;
							case 3:
								outchar(BS);
								outchar('=');
								c=0;
								break;
						}
						break;
					case 3:
						switch(c++) {
							case 0:
								outchar(BS);
								outchar('Ú');
								break;
							case 1:
								outchar(BS);
								outchar('À');
								break;
							case 2:
								outchar(BS);
								outchar('Ù');
								break;
							case 3:
								outchar(BS);
								outchar('¿');
								c=0;
								break;
						}
						break;
					case 4:
						switch(c++) {
							case 0:
								outchar(BS);
								outchar('Ü');
								break;
							case 1:
								outchar(BS);
								outchar('Þ');
								break;
							case 2:
								outchar(BS);
								outchar('ß');
								break;
							case 3:
								outchar(BS);
								outchar('Ý');
								c=0;
								break;
						}
						break; 
				}
			}
		}
		ch=inkey(mode,mode&K_SPIN ? 250:1000);
		if(sys_status&SS_ABORT)
			return(0);
		now=time(NULL);
		if(ch) {
			if(mode&K_NUMBER && isprint(ch) && !isdigit(ch))
				continue;
			if(mode&K_ALPHA && isprint(ch) && !isalpha(ch))
				continue;
			if(mode&K_NOEXASC && ch&0x80)
				continue;
			if(mode&K_SPIN)
				backspace();
			if(mode&K_COLD && ch>' ' && useron.misc&COLDKEYS) {
				if(mode&K_UPPER)
					outchar(toupper(ch));
				else
					outchar(ch);
				while((coldkey=inkey(mode,1000))==0 && online && !(sys_status&SS_ABORT))
					;
				backspace();
				if(coldkey==BS || coldkey==DEL)
					continue;
				if(coldkey>' ')
					ungetkey(coldkey); 
			}
			if(mode&K_UPPER)
				return(toupper(ch));
			return(ch); 
		}
		if(sys_status&SS_USERON && !(sys_status&SS_LCHAT)) gettimeleft();
		else if(online && now-answertime>SEC_LOGON && !(sys_status&SS_LCHAT)) {
			console&=~(CON_R_ECHOX|CON_L_ECHOX);
			console|=(CON_R_ECHO|CON_L_ECHO);
			bputs(text[TakenTooLongToLogon]);
			hangup(); 
		}
		if(sys_status&SS_USERON && online && (timeleft/60)<(5-timeleft_warn)
			&& !SYSOP && !(sys_status&SS_LCHAT)) {
			timeleft_warn=5-(timeleft/60);
			SAVELINE;
			attr(LIGHTGRAY);
			bprintf(text[OnlyXminutesLeft]
				,((ushort)timeleft/60)+1,(timeleft/60) ? "s" : nulstr);
			RESTORELINE; 
		}

		if(!(startup->options&BBS_OPT_NO_TELNET_GA)
			&& now!=last_telnet_cmd && now-timeout>=60 && !((now-timeout)%60)) {
			// Let's make sure the socket is up
			// Sending will trigger a socket d/c detection
			send_telnet_cmd(TELNET_GA,0);
			last_telnet_cmd=now;
		}
			
		if(online==ON_REMOTE && !(console&CON_NO_INACT)
			&& now-timeout>=cfg.sec_warn) { 					/* warning */
			if(sys_status&SS_USERON && cfg.sec_warn!=cfg.sec_hangup) {
				SAVELINE;
				bputs(text[AreYouThere]); 
			}
			else
				bputs("\7\7");
			while(!inkey(K_NONE,100) && online && now-timeout>=cfg.sec_warn) {
				now=time(NULL);
				if(now-timeout>=cfg.sec_hangup) {
					if(online==ON_REMOTE) {
						console|=CON_R_ECHO;
						console&=~CON_R_ECHOX; 
					}
					bputs(text[CallBackWhenYoureThere]);
					logline(nulstr,"Inactive");
					hangup();
					return(0); 
				}
			}
			if(sys_status&SS_USERON && cfg.sec_warn!=cfg.sec_hangup) {
				bputs("\r\1n\1>");
				RESTORELINE; 
			}
			timeout=now; 
		}

	} while(online);

	return(0);
}


/****************************************************************************/
/* Outputs a string highlighting characters preceeded by a tilde            */
/****************************************************************************/
void sbbs_t::mnemonics(char *str)
{
    char *ctrl_a_codes;
    long l;

	if(!strchr(str,'~')) {
		mnestr=str;
		bputs(str);
		return; 
	}
	ctrl_a_codes=strchr(str,1);
	if(!ctrl_a_codes) {
		if(str[0]=='@' && str[strlen(str)-1]=='@' && !strchr(str,' ')) {
			mnestr=str;
			bputs(str);
			return; 
		}
		attr(cfg.color[clr_mnelow]); 
	}
	l=0L;
	while(str[l]) {
		if(str[l]=='~' && str[l+1]!=0) {
			if(!term_supports(ANSI))
				outchar('(');
			l++;
			if(!ctrl_a_codes)
				attr(cfg.color[clr_mnehigh]);
			outchar(str[l]);
			l++;
			if(!term_supports(ANSI))
				outchar(')');
			if(!ctrl_a_codes)
				attr(cfg.color[clr_mnelow]); 
		}
		else {
			if(str[l]==CTRL_A           /* ctrl-a */
				&& str[l+1]!=0)	{		/* valid */
				ctrl_a(str[++l]);       /* skip the ctrl-a */
				l++;                    /* skip the attribute code */
			} else
				outchar(str[l++]); 
		} 
	}
	if(!ctrl_a_codes)
		attr(cfg.color[clr_mnecmd]);
}

/****************************************************************************/
/* Prompts user for Y or N (yes or no) and CR is interpreted as a Y         */
/* Returns 1 for Y or 0 for N                                               */
/* Called from quite a few places                                           */
/****************************************************************************/
bool sbbs_t::yesno(char *str)
{
    char ch;

	strcpy(question,str);
	SYNC;
	if(useron.misc&WIP) {
		strip_ctrl(question);
		menu("yesno"); 
	}
	else
		bprintf(text[YesNoQuestion],str);
	while(online) {
		if(sys_status&SS_ABORT)
			ch=text[YN][1];
		else
			ch=getkey(K_UPPER|K_COLD);
		if(ch==text[YN][0] || ch==CR) {
			if(bputs(text[Yes]))
				CRLF;
			lncntr=0;
			return(true); 
		}
		if(ch==text[YN][1]) {
			if(bputs(text[No]))
				CRLF;
			lncntr=0;
			return(false); 
		} 
	}
	return(true);
}

/****************************************************************************/
/* Prompts user for N or Y (no or yes) and CR is interpreted as a N         */
/* Returns 1 for N or 0 for Y                                               */
/* Called from quite a few places                                           */
/****************************************************************************/
bool sbbs_t::noyes(char *str)
{
    char ch;

	strcpy(question,str);
	SYNC;
	if(useron.misc&WIP) {
		strip_ctrl(question);
		menu("noyes"); 
	}
	else
		bprintf(text[NoYesQuestion],str);
	while(online) {
		if(sys_status&SS_ABORT)
			ch=text[YN][1];
		else
			ch=getkey(K_UPPER|K_COLD);
		if(ch==text[YN][1] || ch==CR) {
			if(bputs(text[No]))
				CRLF;
			lncntr=0;
			return(true); 
		}
		if(ch==text[YN][0]) {
			if(bputs(text[Yes]))
				CRLF;
			lncntr=0;
			return(false); 
		} 
	}
	return(true);
}

/****************************************************************************/
/* Waits for remote or local user to hit a key that is contained inside str.*/
/* 'str' should contain uppercase characters only. When a valid key is hit, */
/* it is echoed (upper case) and is the return value.                       */
/* Called from quite a few functions                                        */
/****************************************************************************/
long sbbs_t::getkeys(char *keys, ulong max)
{
	char	str[81];
	uchar	ch,n=0,c=0;
	ulong	i=0;

	SAFECOPY(str,keys);
	strupr(str);
	while(online) {
		ch=getkey(K_UPPER);
		if(max && ch>0x7f)  /* extended ascii chars are digits to isdigit() */
			continue;
		if(sys_status&SS_ABORT) {   /* return -1 if Ctrl-C hit */
			attr(LIGHTGRAY);
			CRLF;
			lncntr=0;
			return(-1); 
		}
		if(ch && !n && (strchr(str,ch))) {  /* return character if in string */
			outchar(ch);
			if(useron.misc&COLDKEYS && ch>' ') {
				while(online && !(sys_status&SS_ABORT)) {
					c=getkey(0);
					if(c==CR || c==BS || c==DEL)
						break; 
				}
				if(sys_status&SS_ABORT) {
					CRLF;
					return(-1); 
				}
				if(c==BS || c==DEL) {
					backspace();
					continue; 
				} 
			}
			attr(LIGHTGRAY);
			CRLF;
			lncntr=0;
			return(ch); 
		}
		if(ch==CR && max) {             /* return 0 if no number */
			attr(LIGHTGRAY);
			CRLF;
			lncntr=0;
			if(n)
				return(i|0x80000000L);		 /* return number plus high bit */
			return(0); 
		}
		if((ch==BS || ch==DEL) && n) {
			backspace();
			i/=10;
			n--; 
		}
		else if(max && isdigit(ch) && (i*10)+(ch&0xf)<=max && (ch!='0' || n)) {
			i*=10;
			n++;
			i+=ch&0xf;
			outchar(ch);
			if(i*10>max && !(useron.misc&COLDKEYS)) {
				attr(LIGHTGRAY);
				CRLF;
				lncntr=0;
				return(i|0x80000000L); 
			} 
		} 
	}
	return(-1);
}

/****************************************************************************/
/* Prints PAUSE message and waits for a key stoke                           */
/****************************************************************************/
void sbbs_t::pause()
{
	char	ch;
	uchar	tempattrs=curatr; /* was lclatr(-1) */
    int		i,j;
	long	l=K_UPPER;

	if(sys_status&SS_ABORT)
		return;
	lncntr=0;
	if(online==ON_REMOTE)
		rioctl(IOFI);
	bputs(text[Pause]);
	j=bstrlen(text[Pause]);
	if(sys_status&SS_USERON && !(useron.misc&(WIP|NOPAUSESPIN))
		&& !(cfg.node_misc&NM_NOPAUSESPIN))
		l|=K_SPIN;
	ch=getkey(l);
	if(ch==text[YN][1] || ch=='Q')
		sys_status|=SS_ABORT;
	else if(ch==LF)	// down arrow == display one more line
		lncntr=rows-2;
	if(text[Pause][0]!='@')
		for(i=0;i<j;i++)
			backspace();
	getnodedat(cfg.node_num,&thisnode,0);
	nodesync();
	attr(tempattrs);
}

/****************************************************************************/
/* Puts a character into the input buffer                                   */
/****************************************************************************/
void sbbs_t::ungetkey(char ch)
{
#if 0	/* this way breaks ansi_getxy() */
	RingBufWrite(&inbuf,(uchar*)&ch,sizeof(uchar));
#else
	keybuf[keybuftop++]=ch;   
	if(keybuftop==KEY_BUFSIZE)   
		keybuftop=0; 
#endif
}
