/*
 *			M S G 3 . C
 *
 * This is the third part of the MSG program.
 *
 * Functions -
 *	xeq		run a sub-process
 *	help		supply help
 *	gitr		get message range
 *	gitrtype	get message range type details
 *	gitrnum		get message range -- numeric
 *	unset		clear all "Process it" flags
 *	cntproc		count number of messages to be processed
 *	setrange	mark group of messages to process
 *	settype		mark all messages according to type command
 *	getnum		input a decimal number
 *	doiter		apply a function to all marked messages
 *	resend		resend message to addresses
 *	sortbox		sort msg box
 *	qcomp		qsort comparison routine
 *
 *		R E V I S I O N   H I S T O R Y
 *
 *	06/09/82  MJM	Split the enormous MSG program into pieces.
 *
 *	03/16/83  MJM	Decoupled "inverse" from "all".
 */
#include "util.h"
#include "mmdf.h"
#include <pwd.h>
#include <signal.h>
#include <sys/stat.h>
#ifdef LINUX
#include <bsd/sgtty.h>
#else
#include <sgtty.h>
#endif /* LINUX */
#ifdef V4_2BSD
#include <sys/wait.h>
#endif V4_2BSD
#include "./msg.h"

/*
 *			X E Q
 *
 *  Execute some program, such as SEND or the Shell.
 */
sigtype (*old2)();
sigtype (*old3)();
sigtype (*old13)();
sigtype (*old18)();

xeq( exflag)
char exflag;
{
	int process;
#ifdef V4_2BSD
	union wait pstatus;
#else V4_2BSD
	int	pstatus;
#endif V4_2BSD
	char *routine;

	tt_norm();
	old2 = signal( SIGINT, SIG_IGN);
	old13 = signal( SIGPIPE, SIG_IGN);
#ifdef SIGTSTP
	old18 =	signal( SIGTSTP, SIG_DFL);
#endif SIGTSTP

	process = fork();
	if( !process)  {
		int	fd;

		signal( SIGINT, orig);
		signal( SIGQUIT, old3);
		signal( SIGPIPE, old13);

#ifndef V4_2BSD
		for (fd = _NFILE-1; fd > 2; fd--)
#else
		for (fd = getdtablesize()-1; fd > 2; fd--)
#endif
			close (fd);

		switch( exflag)  {

		case 'a': 		  /* answer send fork */
			execlp( routine = sndname, sndname, sndto, "-c", sndcc,
				"-s", sndsubj, (char *)0);
			break;

		case 'A':
			/* Invoke SEND after 2-window edit */
			execlp( routine = sndname, sndname, sndto, "-c", sndcc,
				"-s", sndsubj, "-f", draft_work, "-n", (char *)0);
			break;

		case 'd': 
			execlp( routine = "date", "date", (char *)0);
			break;

		case 'e':		/* 2-window edit */
			execlp( routine = twowinfil, ueditor,
				draft_original, draft_work, (char *)0 );
			break;

		case 'f': 		  /* forward send fork */
			execlp( routine = sndname, sndname, "-f", outfile,
			  "-c", (isnull( sndsubj[0]) ? (char *)0 : "-s"),
			  sndsubj, (char *)0);
			break;

		case 'j': 		  /* One command shell fork */
			if( verbose)  {
				printf( "jump into lower shell running: ");
				fflush(stdout);
			}
			execlp( routine = ushell, ushell, "-t", (char *)0);
			break;

		case 's':		/* shell fork */
			execlp( routine = ushell, ushell, (char *)0);
			break;

		case '!':		/* shell fork */
			execlp( routine = ushell, ushell, "-c", key, (char *)0 );
			break;

		case 'S': 		  /* send fork */
			execlp( routine = sndname, sndname, (char *)0);
			break;
		}
		fprintf (stderr, "Can't execute %s\r\n", routine);
		closeup(-1);
	}  else  {
		while (wait( &pstatus ) != -1) ;

		signal( SIGINT, old2);
		signal( SIGQUIT, SIG_IGN);
		signal( SIGPIPE, old13);
#ifdef SIGTSTP
		signal( SIGTSTP, old18);
#endif SIGTSTP
		tt_init();		/* in case he changed the ttymodes */
	}
	tt_raw();
}

/*--------------------------------------------------------------------*/

char *cmdlst[] = {
	"",
	"Command Summary -- Type only the first letter of a command",
	"",
	"MESSAGE HANDLING:			    NAVIGATION:",
	"Answer [message #(s)]			    Backup to prev msg & type it",
	"Forward [message #(s)]			    Next message & type it",
	"Send (compose) a new message		    Headers of [message #(s)]",
	"Type [message #(s)] onto terminal	    Go to message #",
	"Delete [message #(s)]			    Current message # is typed",
	"Undelete [message #(s)]",		
	"Keep [message #(s)]                        /-same as dcn",
	"Y-Resend [message #(s)]",
	"Z-Two window answer",
	"",
	"FILE HANDLING:				    MISCELLANEOUS:",
	"Exit and update -- normal exit		    Jump into sub-Shell",
	"List [message #(s)] into text file	    Xtra user options",
	"Overwrite old file			    : current date and time",
	"Put [message #(s)] into msg file	    ; ignore rest of line",
	"Quit -- fast exit			    @ Undigestify",
	"Read another msg file			    ! sub-Shell",
	"Move [message #(s)] into msg file & mark as deleted",
	"",
	"Examples of message #(s) are:  42  1-4  2,5,7-.  1-5,7-@  c  32-$",
	0
};

char *xtcmdlst[] = {
	"",
	"xtra options command summary -- Type only the first letter of a command",
	"",
	"Binary file write",
	"Control char filter (on/off)",
	"Exit without saves",
	"List message body",
	"Mark message",
	"Numbered message lists (on/off)",
	"Output mbox file",
	"Paging (on/off)",
	"Reorder (sort) msg file",
	"Strip keywords (on/off)",
	"Verbose (on/off)",
	"Xtra options status",
	0
};

/*
 *			H E L P
 */
help(helplist)
char *helplist[];
{
	register unsigned int i;

	tt_norm();
	for( i = 0; helplist[i] != 0; i++)
		printf( "%s\r\n", helplist[i]);
	tt_raw();
}

/*--------------------------------------------------------------------*/

/*
 *			G I T R
 *
 *  Get message range.
 */
gitr()
{
	if( status.ms_nmsgs == 0)  {
		printf( "'%s' is empty\r\n", filename);
		error( "" );
	}

	unset();

	if( !gitrtype())
		gitrnum();

	if( cntproc() == 0)
		error( " no such messages\r\n");
}

/*
 *			G I T R T Y P E
 *
 * Get range type.  Alphabetic answers are processed here,
 * numerics are left alone (FALSE return).
 */
gitrtype()
{
	char cmd;

	cmd = nxtchar;
	nxtchar = rdnxtfld();
	nxtchar = uptolow(nxtchar);

	if( cmd == 'g' )  {
		/* Only numerics valid for GOTO command */
		if( isalpha( nxtchar) && nxtchar != 'c' && nxtchar != 'm')
			error( "...not legal for this command\r\n");
	}

	switch( nxtchar)  {
	case 'a': 
		if( verbose)
			printf( "all");
		if( cmd != 'h' ) {
			if( !confirm((char *)0,DOLF) )
				error("");
		}
		else if (verbose)
			printf("\r\n");
		setrange( 1, status.ms_nmsgs);
		break;

	case 'c': 
		if( verbose)
			printf( "current\r\n");
		if( status.ms_curmsg == 0)  {
			if( status.ms_nmsgs != 0)
				status.ms_curmsg = 1;
			else
				if( status.ms_curmsg > status.ms_nmsgs)
					error( "no current message\r\n");
		}
		setrange( status.ms_curmsg, status.ms_curmsg);
		break;

	case 'd': 
		if( verbose)
			printf( "deleted messages\r\n");
		settype( 'd');
		if( cntproc() == 0)
			error( "no deleted messages\r\n");
		break;

	case 'e': 
		if( verbose)
			printf( "expression: ");
		key[0] = rdnxtfld();
		gather( key, 79);
		if (key[0])
			settype( 'e');
		break;

	case 'f': 
		if( verbose)
			printf( "from: ");
		key[0] = rdnxtfld();
		gather( key, 79);
		if (key[0])
			settype( 'f');
		break;

	case 'i': 
		if( verbose)
			printf( "inverse ");
		ascending = FALSE;	/* flag for doiter() */

		if( !gitrtype() )	/* RECURSE */
			gitrnum();
		return( TRUE );

	case 'k':
		if( verbose )
			printf( "kept messages" );

		if( cmd == 'd' ) {
			if( !confirm((char *)0,DOLF) )
				error("");
		}
		else if (verbose)
			printf("\r\n");

		settype( 'k' );
		break;

	case 'l':
		if( verbose)
			printf( "leaving" );

		if( !ismainbox )
			error( "\r\nNot main mailbox - no messages will leave\r\n");

		if( cmd == 'd' ) {
			if( !confirm((char *)0,DOLF) )
				error("");
		}
		else if (verbose)
			printf("\r\n");

		settype( 'l' );
		break;

	case 'm':
		if(verbose)
			printf( "mark\r\n");
		if( status.ms_markno > 0 && status.ms_markno <= status.ms_nmsgs )
			setrange(status.ms_markno, status.ms_markno);
		else
			error("no mark set\r\n");
		break;

	case 'n':
		if( verbose)
			printf( "new");

		if( cmd == 'd' ) {
			if( !confirm((char *)0,DOLF) )
				error("");
		}
		else if (verbose)
			printf("\r\n");

		settype( 'n' );
		break;

	case 's': 
		if( verbose)
			printf( "subject: ");
		key[0] = rdnxtfld();
		gather( key, 79);
		if (key[0])
			settype( 's');
		break;

	case 't': 
		if( verbose)
			printf( "to: ");
		key[0] = rdnxtfld();
		gather( key, 79);
		if (key[0])
			settype( 't');
		break;

	case 'u': 
		if( verbose)
			printf( "undeleted messages");

		if( cmd == 'd' ) {
			if( !confirm((char *)0,DOLF) )
				error("");
		}
		else if (verbose)
			printf("\r\n");

		settype( 'u');
		if( cntproc() == 0)
			error( "no undeleted messages\r\n");
		break;

	case '?':
		printf( "\r\na message sequence:\r\n");
		printf( " a(ll), c(urrent), d(eleted), e(xpression), f(rom), l(eaving),\r\n");
		printf( " k(ept), m(ark), n(new), i(inverse), s(ubject), t(o), u(ndeleted)\r\n");
		printf( " or a number or list of numbers of the form\r\n");
		printf( " (#  #-#  #,#,#,#  $ .-$)  .=current @=mark $=last\r\n" );
		error( "" );

	case '\003':
		error(" ^C\r\n");
		
	case '\004':
		error(" ^D\r\n");

	case '\n': 
		if( status.ms_curmsg == 0)  {
			if( status.ms_nmsgs != 0)
				status.ms_curmsg = 1;
			else
				if( status.ms_curmsg > status.ms_nmsgs)
					error( "no current message\r\n");
		}
		if( cmd == 'g' && status.ms_markno > 0 && status.ms_markno <= status.ms_nmsgs )
			status.ms_curmsg = status.ms_markno;
		setrange( status.ms_curmsg, status.ms_curmsg);
		switch( cmd)  {

		case 'a': 
		case 'f': 
		case 'h': 
		case 't': 
		case 'y':
		case 'z':
			if (verbose) printf("\r\n");
			break;	  /* message number is printed in header */

		default: 
			/* Print brief header */
			printf( "\r\n # %d  %s\r\n", status.ms_curmsg,
				msgp[status.ms_curmsg-1]->from);
		}			  /* DROP ON THROUGH */
		break;

	default: 
		return( FALSE);
	}
	if (!verbose) suckup();
	return( TRUE);
}

/*
 *			G I T R N U M
 *
 * Get range -- numeric case
 */
gitrnum()
{
	unsigned int getn;
	unsigned int mbegin, mend;

	key[0] = nxtchar;
	gather( key, 78 );
	gc = key;

	while( *gc != '\000' )  {
		switch( *gc )  {

		case ' ':
		case ',':
			gc++;
		}

		if( *gc == '\000')
			break;

		getn = getnum( &mbegin);
		mend = mbegin;

		switch( *gc )  {

		case '\000': 
		case ' ': 
		case ',': 
			if( mbegin == 0 || mbegin > status.ms_nmsgs)
				error( "bad number (type ? for help)\r\n");
			break;

		case '>': 
		case '-': 		  /* ;2 Replace range delimiters */
		case ':': 		  /* ;2 Replace range delimiters */
			if( getn == 0)
				mbegin = 1;
			if( mbegin == 0 || mbegin > status.ms_nmsgs)
				error( " bad first number (type ? for help)\r\n");

			gc++;
			getn = getnum( &mend);

			if( getn == 0)
				mend = status.ms_nmsgs;
			else
				if( mend > status.ms_nmsgs)  {
					printf( "... %d is last message\r\n", status.ms_nmsgs);
					mend = status.ms_nmsgs;
				}
			if( mend == 0)
				error( "bad last number (type ? for help)\r\n");
			break;

		default: 
			error( " bad message number (type ? for help)\r\n");
		}
		setrange( mbegin, mend);
	}
}

/*--------------------------------------------------------------------*/

/*
 *			U N S E T
 *
 * Clear all the M_PROCESS_IT flags.  Used prior to setting ranges, etc.
 */
unset()
{
	register struct message **mp;

	/* set all the flags to zero */
	for( mp = &msgp[status.ms_nmsgs]; mp-- != &msgp[0]; )
		(*mp)->flags &= ~M_PROCESS_IT;
}

/*
 *			C N T P R O C
 *
 * Count how many messages to process.
 */
cntproc()
{
	register unsigned int nproc;
	register struct message **mp;

	nproc = 0;
	for( mp = &msgp[status.ms_nmsgs]; mp-- != &msgp[0]; )
		if( (*mp)->flags & M_PROCESS_IT)
			nproc++;
	return( nproc);
}

/*
 *			S E T R A N G E 
 *
 * set group of messages to process
 */
setrange( mbegin, mend )
register unsigned int mbegin;
register unsigned int mend;
{
	if( mbegin == 0 )
		mbegin = 1;
	if( mend > Nmsgs )
		mend = Nmsgs;

	while( mend-- >= mbegin)
		msgp[mend]->flags |= M_PROCESS_IT;
}

/*
 *			S E T T Y P E
 */
settype( type )
char type;
{
	register struct message **mp;
	register unsigned int i;

	switch( type )  {

	case 'd':
		for( mp = &msgp[status.ms_nmsgs]; mp-- != &msgp[0]; )
			if( (*mp)->flags & M_DELETED)
				(*mp)->flags |= M_PROCESS_IT;
		break;

	case 'n':
		for( mp = &msgp[status.ms_nmsgs]; mp-- != &msgp[0]; )
			if( (*mp)->flags & M_NEW)
				(*mp)->flags |= M_PROCESS_IT;
		break;

	case 'k':
		for( mp = &msgp[status.ms_nmsgs]; mp-- != &msgp[0]; )
			if( (*mp)->flags & M_KEEP)
				(*mp)->flags |= M_PROCESS_IT;
		break;

	case 'u':
		for( i = status.ms_nmsgs; i-- != 0;)
			if( !(msgp[i]->flags & M_DELETED))
				msgp[i]->flags |= M_PROCESS_IT;
		break;

	case 's':
		for( i = status.ms_nmsgs; i-- != 0; )
			if( strindex( key, msgp[i]->subject) >= 0)
				msgp[i]->flags |= M_PROCESS_IT;
		break;

	case 'f':
		for( i = status.ms_nmsgs; i-- != 0; )
			if( prefix( msgp[i]->from, "To:"))  {
				if( prefix( key, username))
					msgp[i]->flags |= M_PROCESS_IT;
			}  else  {
				if( strindex( key, msgp[i]->from) >= 0)
					msgp[i]->flags |= M_PROCESS_IT;
			}
		break;

	case 't':
		for( i = status.ms_nmsgs; i-- != 0; )
			if( strindex( key, msgp[i]->to ) >= 0 )
				msgp[i]->flags |= M_PROCESS_IT;
		break;

	case 'e':
		tt_norm();
		for( msgno = 0; msgno < status.ms_nmsgs; msgno++)
			if(txtmtch())
				msgp[msgno]->flags |= M_PROCESS_IT;
		tt_raw();
		break;

	case 'l':
		tt_norm();
		for( mp = &msgp[status.ms_nmsgs]; mp-- != &msgp[0]; )
			if(    !((*mp)->flags & M_KEEP)
			    && !((*mp)->flags & M_DELETED)
			    && !((*mp)->flags & M_NEW))
				(*mp)->flags |= M_PROCESS_IT;
		tt_raw();
		break;

	default:
		error("bad settype() call");
	}
}

/*
 *			G E T N U M
 */
getnum( result)
unsigned int *result;
{
	register unsigned int i;
	register unsigned int n;

	i = n = 0;
	while( *gc == ',' || *gc == '-' || *gc == '>' || *gc == ':' )
		gc++;
	if( *gc == '$')  {
		*result = status.ms_nmsgs;
		gc++;
		return( TRUE);
	} else if ( *gc == '.') {
		*result = status.ms_curmsg;
		gc++;
		return( TRUE );
	} else if ( *gc == '@' ) {
		if( status.ms_markno > 0 && status.ms_markno <= status.ms_nmsgs )
			*result = status.ms_markno;
		else
			error("no mark set\r\n");
		gc++;
		return( TRUE );
	}
	while( isdigit( *gc ) )  {
		n = n * 10 + *gc - '0';
		i++;
		gc++;
	}

	*result = n;
	return( i);
}

/*--------------------------------------------------------------------*/

/*
 *			D O I T E R
 *
 * do iteration on message numbers
 */
doiter( fn)
int ( *fn)();
{
	unsigned int lastdone;

	if( status.ms_nmsgs == 0)
		return;
	tt_norm();
	lastdone = msgno;
	if( ascending == TRUE )  {
		/* process in ascending order */
		for( msgno = 1; msgno <= status.ms_nmsgs; msgno++)
			if( (mptr = msgp[msgno-1])->flags & M_PROCESS_IT)  {
				(*fn)();
				lastdone = msgno;
			}
	}  else  {
		/* Process in inverse (descending) order */
		for( msgno = status.ms_nmsgs; msgno > 0; msgno-- )
			if( (mptr = msgp[msgno-1])->flags & M_PROCESS_IT)  {
				(*fn)();
				lastdone = msgno;
			}
	}
	tt_raw();
	msgno = lastdone;
}

/*-----------------------------------------------------------------------
 *  			R E S E N D M S G
 *  
 *  Resend message to addressees
 */
resendmsg() {

	register int savepretty;

	if( verbose )
		putchar('.');
	lstsep = FALSE;
	lstmore = FALSE;
	savepretty = prettylist;
	prettylist = OFF;
	cpyiter( lstmsg, NOIT,( int( *)()) 0);
	prettylist = savepretty;
	mptr->flags |= M_FORWARDED;
}
/*			S O R T B O X
 *  
 */
sortbox() {

	register int i,j,k;
	struct message *mpt;
	char *pi, *pj;

	if( status.ms_nmsgs == 0)  {
		printf( "'%s' is empty\r\n", filename);
		error( "" );
	}

	nxtchar = rdnxtfld();
	nxtchar = uptolow(nxtchar);
	switch( nxtchar)  {

	case 'd':
		if( verbose )
			printf("Date\r\n");
		break;

	case 'f': 
		if( verbose)
			printf("From\r\n");
		break;

	case 'r':
		if( verbose )
			printf("date and subject\r\n");
		fflush(stdout);
		/* sort by date first */
		nxtchar = 'd';
		qsort( &msgp[0], status.ms_nmsgs, sizeof(msgp[0]), qcomp );

		/* Match subjects */
		for( i = 0; i < status.ms_nmsgs-1; i++ ) {
			if( msgp[i]->subject[0] == '\0' )
				continue;
			for( pi = msgp[i]->subject; prefix( pi, "Re:" ) == TRUE; )
				pi = strend("Re:",pi);

			for( j = i+1; j < status.ms_nmsgs; j++ ) {
				if( msgp[j]->subject[0] == '\0' )
					continue;
				for( pj = msgp[j]->subject; prefix( pj, "Re:" ) == TRUE; )
					pj = strend("Re:",pj);

				if( lexnequ( pi, pj, SIZESUBJ ) != TRUE )
					continue;

				/* Match - move rest down a slot */
				if( j == i+1 ) {	/* No need to move */
					i++;
					continue;
				}
				mpt = msgp[j];
				for( k = j-1; k > i; k-- )
					msgp[k+1] = msgp[k];
				msgp[i+1] = mpt;
				i++;
			}
		}
		return;

	case 's': 
		if( verbose)
			printf( "Subject\r\n");
		break;

	case 't':
		if( verbose )
			printf("To\r\n");
		break;

	case '?':
		printf( "\r\nd(ate), f(rom), r( date & subject), s(ubject), t(o)\r\n");
		error( "" );

	case '\004':
		error( " ^D\r\n" );

	default:
		error( "" );
	}

	fflush(stdout);
	qsort( &msgp[0], status.ms_nmsgs, sizeof(msgp[0]), qcomp);
}

/*			Q C O M P
 *  
 *  Comparison routine called by qsort
 */
qcomp(pa,pb)
struct message **pa, **pb;
{
	int sval;
	char *cpa, *cpb;
	
	switch( nxtchar ) {

	case 'd':
		return(datecmp((*pa)->date, (*pb)->date));

	case 'f': 
		return(lexrel( (*pa)->from, (*pb)->from ));

	case 's': 
		/* Remove all Re: */
		for( cpa = (*pa)->subject; prefix( cpa, "Re:" ) == TRUE; )
			cpa = strend("Re:",cpa);
		for( cpb = (*pb)->subject; prefix( cpb, "Re:" ) == TRUE; )
			cpb = strend("Re:",cpb);
		if( (sval = lexrel( cpa, cpb )) == 0 )
			return(datecmp((*pa)->date, (*pb)->date));
		else
			return(sval);

	case 't':
		return( lexrel( (*pa)->to, (*pb)->to ) );

	default:
		error( "Bad type in qcomp" );
	}
	/*NOTREACHED*/
}

datecmp(a, b)
long a, b;
{
	if ((a - b) < 0)
		return (-1);
	if ((a - b) > 0)
		return (1);
	return(0);
}
