/*
** vn news reader.
**
** reader.c - article reading interface - "more" like.
**
** see copyright disclaimer / history in vn.c source file
*/

#include <stdio.h>
#include <sys/types.h>
#include <ctype.h>
#include <time.h>
#include "tty.h"
#include "config.h"
#include "tune.h"
#include "head.h"
#include "reader.h"
#include "brk.h"
#include "node.h"
#include "page.h"
#include "vn.h"

#ifdef amiga
#define system(a)	Execute(a, 0, 0)
extern char pr_buf[];
extern ttputc ();
#endif

#define PERTAB 8	/* tab expansion factor */
#define BACKTRACK 24

extern char *Printer,*Editor,*Mailer,*Poster,*Orgdir,*Savefile,*Savedir,*Ccfile;
extern int L_allow;
extern int C_allow;
extern int Rot;
extern int Headflag;
extern int Digest;
extern int More;
extern char *No_msg;
extern char *Roton_msg;
extern char *Rotoff_msg;
extern char *Hdon_msg;
extern char *Hdoff_msg;
extern char *Def_smode;

extern PAGE Page;

extern int (*Massage)();
extern int (*Postfunc)();
extern int (*Mailfunc)();
extern int (*Savemode)();
extern int (*Hookfunc)();

extern char Cxrtoi[], Cxitor[];

static FILE *Fpread;
static char *Fname;
static char *Lookahead;
static int Rlines;
static int Hlines;
static long Rew_pos;

static void save2_article (), followup (), printr (), searcher (), mail ();
static saver (), edcopy (), rot_line (), do_out (), rerrmsg ();
static char getpgch (), *rprompt ();


/*
	readfile presents article:
		fn = name of article.
		sn - name of next article, NULL if last.
		pages - pages to advance on return, if applicable
	returns 0 for "continue", <0 for "quit"

	calls sig_set(BRK_READ / BRK_RFIN) around reading article.
*/
readfile (fn,sn,pages)
char *fn;
char *sn;
int *pages;
{
	ARTHEADER hdr;
	FILE *fopen();
	int lines,percent;
	int i;
	int top, bottom;
	int step;
	char c,  buf[RECLEN];
	char lasave[RECLEN];
	char pstr[24], dgname[48];
	char getpgch(), *index(), *dige_extract(), *tgetstr();
	char *any;
	FILE *vns_aopen();
	long ftell();

	Fname = fn;
	*pages = 0;
	step = FALSE;	/* Boolean; to indicate user input a <RET> (PG_STEP) */

	term_set(ERASE);
	sig_set(BRK_READ,&Fpread);

	any = "any key to continue .... ";

	if (Digest)
	{
		lines = atoi(Fname);
		if ((Fname = dige_extract(dgname,lines,&hdr,&Rew_pos)) == NULL)
		{
			rerrmsg("couldn't extract article %d from digest",lines);
#ifdef amiga
			sprintf(pr_buf,any);
			tputs(pr_buf,1,ttputc);
#else
			printf(any);
#endif
			getnoctl();
			sig_set(BRK_RFIN);
			return (0);
		}
		if ((Fpread = fopen(Fname,"r")) == NULL)
		{
			rerrmsg("couldn't open %s",Fname);
#ifdef amiga
			sprintf(pr_buf,any);
			tputs(pr_buf,1,ttputc);
#else
			printf(any);
#endif
			getnoctl();
			sig_set(BRK_RFIN);
			return (0);
		}
  		fseek(Fpread,Rew_pos,0);
	}
	else
	{
		if ((Fpread = vns_aopen(atoi(Fname),&hdr)) == NULL)
		{
			rerrmsg("couldn't open article %s",Fname);
#ifdef amiga
			sprintf(pr_buf,any);
			tputs(pr_buf,1,ttputc);
#else
			printf(any);
#endif
			getnoctl();
			sig_set(BRK_RFIN);
			return (0);
		}
		Rew_pos = ftell(Fpread);
	}

	Hlines = hdr.hlines;
#ifdef amiga
	sprintf (pr_buf,ANFORM,Fname,Cxrtoi[PG_HELP]);
	tputs(pr_buf,1,ttputc);
#else
	printf (ANFORM,Fname,Cxrtoi[PG_HELP]);
#endif

	lines = 0;

	if (Headflag)
	{
		rewind(Fpread);
		Rlines = 0;
	}
	else
	{
		/* use do_out to guard against control sequences */
		Rlines = Hlines;
		for (i=0; i < hdr.show_num; ++i)
			lines += do_out((hdr.show)[i],L_allow-lines);
		lines += do_out("",L_allow-lines);
	}

	/* will return out of outer while loop */
	Lookahead = NULL;
	while (1)
	{
		/*
		** lines counts folded lines from do_out.
		** globals Hlines and Rlines refer to records.
		** If Lookahead is null after this loop, we've
		** hit EOF.
		*/
		if (Lookahead != NULL && More && !step)
		{
			char *looktmp;

			/*
			** Save Lookahead because `do_out' nukes it.
			** Perhaps we could just use `printf' here
			** but for now we'll play it safe. - GKE 12/26/87
			*/
			looktmp = Lookahead;
			term_set(ERASE);
			/* 
			** The following presents the last line of the
			** previous page in reversed video, as the first line
			** of the current page, `rn'-stylee.  Since `rn'
			** uses an option to enable/disable this, remove the
			** two `term_set's if not to your liking.
			*/
			term_set(ONREVERSE);
			do_out(lasave,1);
			term_set(OFFREVERSE);
			Lookahead = looktmp;
		}
		lines += do_out(Lookahead,L_allow-lines);
		while (1)
		{
			if (Lookahead == NULL)
			{
				if (fgets(buf,RECLEN-1,Fpread) == NULL)
					break;
				Lookahead = buf;
				if (Rot != 0 && Rlines >= Hlines)
					rot_line(buf);
				++Rlines;
			}
			if (lines >= L_allow)
				break;
			if (More)
				strcpy(lasave,Lookahead);
			lines += do_out(Lookahead,L_allow-lines);
		}

		if (Lookahead != NULL)
		{
			/*
			** calculation is truncated rather than rounded,
			** so we shouldn't get "100%".  Subtract 2 for
			** 1 line lookahead and empty line at beginning
			** of article.
			*/
			if (Headflag)
			{
				top = (Rlines-2);
				bottom = hdr.lines + Hlines;
			}
			else
			{
				top = (Rlines-Hlines-2);
				bottom = hdr.lines;
			}
			/*
			** protect against division by zero -
			** shouldn't actually come up zero at this
			** point if vns_aopen is sane. 999 will let user
			** know the percentage is obviously wrong.
			*/
			if (bottom != 0)
				percent = (top*100L)/bottom;
			else
				percent = 999;
			sprintf (pstr,PAGE_MID,percent);
		}
		else
		{
			if (sn == NULL)
				strcpy (pstr,PAGE_END);
			else
			strcpy (pstr,PAGE_NEXT);
		}
		c = getpgch(pstr,&hdr);

		/*
			handle user input:
			CAUTION!!  return cases must close Fpread.
		*/
		step = FALSE;
		switch (c)
		{
		case PG_NEXT:
			if (Digest)
			{
				fclose (Fpread);
				unlink (Fname);
			}
			else
				vns_aclose (Fpread);
			sig_set(BRK_RFIN);
			return (0);
		case PG_FLIP:
			*pages = 1;	/* fall through */
		case PG_QUIT:
			if (Digest)
			{
				fclose (Fpread);
				unlink (Fname);
			}
			else
				vns_aclose (Fpread);
			sig_set(BRK_RFIN);
			return (-1);
		case PG_REWIND:
			if (Headflag)
			{
				Rlines = 0;
				rewind (Fpread);
			}
			else
			{
				fseek (Fpread,Rew_pos,0);
				Rlines = Hlines;
			}
			Lookahead = NULL;
			lines = 2 - RECBIAS;
			break;
		case PG_SEARCH:
			searcher(buf);
			lines = 2 - RECBIAS;
			lines += do_out(buf,L_allow-lines);
			break;
		case PG_WIND:
			fseek (Fpread,0L,2);
			lines = 2 - RECBIAS;
			Lookahead = NULL;
			break;
		case PG_STEP:
			if (Lookahead == NULL)
			{
				if (Digest)
				{
					fclose (Fpread);
					unlink (Fname);
				}
				else
					vns_aclose (Fpread);
				sig_set(BRK_RFIN);
				return (0);
			}
			lines = L_allow - 1;
			step = TRUE;	/* Temporarily disable paging */
			break;
		default:
			if (Lookahead == NULL)
			{
				if (Digest)
				{
					fclose (Fpread);
					unlink (Fname);
				}
				else
					vns_aclose (Fpread);
				sig_set(BRK_RFIN);
				return (0);
			}
			lines = 2 - RECBIAS;
			break;
		}
	}
}

/*
	getpgch prints prompt and gets command from user
	handles "mail", "save" and "followup" internally
	as well as flag resets.
*/
static char
getpgch(prompt,hdr)
char *prompt;
ARTHEADER *hdr;
{
	char c;
	int ic;
	int i;
	char mbuf[RECLEN];

	term_set (ONREVERSE);
#ifdef amiga
	sprintf(pr_buf,"%s\015",prompt);
	tputs(pr_buf,1,ttputc);
#else
	printf("%s\015",prompt);
#endif
	term_set (OFFREVERSE);
	while ((ic=getnoctl()) != EOF)
	{
		c = Cxitor[ic];
		if (Hookfunc != NULL && (i = (*Hookfunc)(c,1,1,mbuf)) >= 0)
		{
			if (i == 0)
			{
				term_set (ZAP,0,PPR_MAX);
#ifdef amiga
				sprintf(pr_buf,"%s\n",mbuf);
				tputs(pr_buf,1,ttputc);
#else
				printf("%s\n",mbuf);
#endif
			}
			term_set (ONREVERSE);
#ifdef amiga
			sprintf(pr_buf,"%s\015",prompt);
			tputs(pr_buf,1,ttputc);
#else
			printf("%s\015",prompt);
#endif
			term_set (OFFREVERSE);
			continue;
		}
		switch (c)
		{
		case SETROT:
			term_set (ZAP,0,PPR_MAX);
			if (Rot == 0)
			{
				Rot = 13;
#ifdef amiga
				sprintf (pr_buf,"%s\n",Roton_msg);
				tputs(pr_buf,1,ttputc);
#else
				printf ("%s\n",Roton_msg);
#endif
			}
			else
			{
				Rot = 0;
#ifdef amiga
				sprintf (pr_buf,"%s\n",Rotoff_msg);
				tputs(pr_buf,1,ttputc);
#else
				printf ("%s\n",Rotoff_msg);
#endif
			}
			if (Lookahead != NULL && Rlines > Hlines)
				rot_line(Lookahead);
			break;
		case HEADTOG:
			term_set (ZAP,0,PPR_MAX);
			if (Headflag)
			{
				Headflag = FALSE;
#ifdef amiga
				sprintf (pr_buf,"%s\n",Hdoff_msg);
				tputs(pr_buf,1,ttputc);
#else
				printf ("%s\n",Hdoff_msg);
#endif
			}
			else
			{
				Headflag = TRUE;
#ifdef amiga
				sprintf (pr_buf,"%s\n",Hdon_msg);
				tputs(pr_buf,1,ttputc);
#else
				printf ("%s\n",Hdon_msg);
#endif
			}
			break;
		case PG_HELP:
			term_set (ZAP,0,PPR_MAX);
			help_rd ();
			break;
		case PG_REPLY:
			mail (hdr);
			break;
		case PG_FOLLOW:
			followup (hdr);
			break;
		case SAVE:
			saver ();
			break;
		case PRINT:
			printr ();
			break;
		default:
			term_set (ZAP,0,PPR_MAX);
			return (c);
		}

		term_set (ONREVERSE);
#ifdef amiga
		sprintf(pr_buf,"%s\015",prompt);
		tputs(pr_buf,1,ttputc);
#else
		printf("%s\015",prompt);
#endif
		term_set (OFFREVERSE);
	}
	term_set (ZAP,0,PPR_MAX);
	return (c);
}

/*
	save article.  Like its "session" counterpart, this loses storage
	if the user specifies a new file, but it should not be significant
*/
static
saver ()
{
	char buf[RECLEN],msg[RECLEN],*str_store();

	user_str (buf,"save file ? ",0,Savefile);
	if (buf[0] != '\0' && buf[0] != '|')
		Savefile = str_store(buf);
	if (save_art(Fname,buf,msg) != 0)
		rerrmsg(msg);
	else {
#ifdef amiga
		sprintf(pr_buf,"%s\n",msg);
		tputs(pr_buf,1,ttputc);
#else
		printf("%s\n",msg);
#endif
	}
}

/*
	invoke editor on new temp file, mail using reply line,
	possibly first allowing user to overide the reply (not INLETTER)
*/
static
void
mail (hdr)
ARTHEADER *hdr;
{
	char *new, fn[L_tmpnam], cmd [RECLEN+60];
	char *rprompt ();
	int i;
	FILE *fp, *fopen();

	if (Massage != NULL)
		(*Massage)(hdr);

	if (hdr->mail_err != NULL)
	{
		rerrmsg(hdr->mail_err);
		return;
	}

	tmpnam (fn);
	if ((fp = fopen(fn,"w")) == NULL)
	{
		rerrmsg("can't open temp file, %s",fn);
		return;
	}

	for (i = 0; i < hdr->mail_num; ++i)
		fprintf(fp,"%s\n",(hdr->mail)[i]);
	fprintf(fp,"\n");

	fprintf(fp,"%s:\n",hdr->from);
	edcopy (fp);
	fclose (fp);

	tty_set (SAVEMODE);

	sprintf (cmd,"%s %s", Editor, fn);
	chdir (Orgdir);
	system (cmd);
	vns_gset (Page.h.name);

	new = rprompt ("still want to mail it ? ",cmd);
	if (new != NULL && (*new == 'y' || *new == 'Y'))
	{
		if (Mailfunc == NULL)
		{
			sprintf (cmd, hdr->mailcmd, fn);
			system (cmd);
#ifdef amiga
			sprintf (pr_buf,"given to mailer\n");
			tputs(pr_buf,1,ttputc);
#else
			printf ("given to mailer\n");
#endif
		}
		else
			(*Mailfunc)(hdr,fn);
	}
	else {
#ifdef amiga
		sprintf (pr_buf,"not mailed\n");
		tputs(pr_buf,1,ttputc);
#else
		printf ("not mailed\n");
#endif
	}

	unlink (fn);

	tty_set (RESTORE);
	term_set (RESTART);
}

/*
	post a followup article, invoking editor for user after creating
	new temp file.  remove after posting.
*/
static
void
followup (hdr)
ARTHEADER *hdr;
{
	char fn[L_tmpnam], *new, cmd [RECLEN], *rprompt();
	int i;
	FILE *f, *fopen();
	char *rindex();

	if (hdr->post_err != NULL)
	{
		rerrmsg(hdr->post_err);
		return;
	}

	tmpnam (fn);
	if ((f = fopen(fn,"w")) == NULL)
	{
		rerrmsg("can't open temp file, %s",fn);
		return;
	}

	for (i=0; i < hdr->post_num; ++i)
		fprintf(f,"%s\n",(hdr->post)[i]);
	fprintf(f,"\n");

	fprintf(f,"From article %s, by %s:\n", hdr->artid, hdr->from);
		
	edcopy (f);
	fclose (f);
	tty_set (SAVEMODE);
	sprintf (cmd,"%s %s", Editor, fn);
	chdir (Orgdir);
	system (cmd);
	vns_gset (Page.h.name);

	new = rprompt("still want to post it ? ",cmd);
	if (new != NULL && (*new == 'y' || *new == 'Y'))
	{
		if (Postfunc == NULL)
		{
			sprintf (cmd, hdr->postcmd, fn);
			system (cmd);
#ifdef amiga
			sprintf (pr_buf,"given to posting program\n");
			tputs(pr_buf,1,ttputc);
#else
			printf ("given to posting program\n");
#endif
		}
		else
			(*Postfunc)(hdr,fn);
		save2_article (fn);
	}
	else {
#ifdef amiga
		sprintf (pr_buf,"not posted\n");
		tputs(pr_buf,1,ttputc);
#else
		printf ("not posted\n");
#endif
	}
	unlink (fn);
	tty_set (RESTORE);
	term_set (RESTART);
}

/*
	get user buffer, return whitespace delimited token
	buffer is allowed to overwrite prompt string.  This routine
	should only be used when the terminal is in cooked mode.
	In raw, use user_str().
*/
static char *
rprompt(s,buf)
char *s,*buf;
{
	char *strtok();

#ifdef amiga
	sprintf(pr_buf,"%s",s);
	tputs(pr_buf,1,ttputc);
#else
	printf("%s",s);
#endif
	fgets (buf,RECLEN-1,stdin);
	return (strtok(buf," \t\n"));
}

/*
	edcopy copies article to file which user is editting for
	a reply or followup, so it may be referenced.  It places
	ED_MARK in the left hand margin.
*/
static
edcopy(fp)
FILE *fp;
{
	long current;
	char buf[RECLEN];

	/* save position, and seek to top of article */
	current = ftell(Fpread);
	fseek (Fpread,Rew_pos,0);

	/* if line already begins with ED_MARK, forget about the space */
	while (fgets(buf,RECLEN-1,Fpread) != NULL)
	{
		if (buf[0] == ED_MARK)
			fprintf(fp,"%c%s",ED_MARK,buf);
		else
			fprintf(fp,"%c %s",ED_MARK,buf);
	}

	/* restore position */
	fseek(Fpread,current,0);
}

static
rot_line (s)
unsigned char *s;
{
	for ( ; *s != '\0'; ++s)
	{
		if (isupper(*s))
		{
			*s += Rot;
			if (!isupper(*s))
				*s -= 26;
			continue;
		}
		if (islower(*s))
		{
			*s += Rot;
			if (!islower(*s))
				*s -= 26;
		}
	}
}

/*
** output record.  folds record to terminal width on word boundaries,
** returning number of lines output.  If limit is reached, remainder
** of buffer waiting to be output is returned.  Processes several
** special characters:
**	form-feed - return "lim" lines so we stop on that line
**	tabs - counts "expanded" width
**	backspace - assumes they work, -1 width unless in first col.
**	bell - pass through with zero width
**	newline - end of record.
**	del - turns into '_'
**	other control - turned into '?'
**		(prevents "letter bombs" containing inappropriate control
**			sequences for the terminal).
**
** Sets Lookahead pointer to remainder of line or NULL.
*/
static
do_out(s,lim)
char *s;
int lim;
{
	int len,i;
	char cs,*word,*start;

	Lookahead = NULL;
	if (s == NULL)
		return(0);
	len = 0;
	start = word = s;

	/*
	** NOTE: "normal" return is buried inside switch, at newline
	** ending record
	*/
	for (i=0; i < lim; ++i)
	{
		for ( ; len < C_allow; ++s)
		{
			switch (*s)
			{
			case '\n':
				*s = '\0';	/* fall through */
			case '\0':
#ifdef amiga
				sprintf(pr_buf,"%s\n",start);
				tputs(pr_buf,1,ttputc);
#else
				printf("%s\n",start);
#endif
				return(i+1);
			case '\t':
				len = ((len/PERTAB)+1)*PERTAB;
				word = s;
				break;
			case '\b':
				if (len > 0)
					--len;
				break;
			case '\014':
				*s = ' ';
				i = lim-1;	/* fall through */
			case ' ':
				word = s+1;
				++len;
				break;
			case '\177':
				*s = '_';
				++len;
				break;
			default:
 				if (iscntrl(*s))
					*s = '?';
				++len;		/* fall through */
			case '\07':
				break;
			}
		}
		cs = *s;
		*s = '\0';
		if ((len = strlen(word)) < BACKTRACK)
		{
			*s = cs;
			s = word;
			cs = *s;
			*s = '\0';
		}
		else
			len = 0;
#ifdef amiga
		sprintf(pr_buf,"%s\n",start);
		tputs(pr_buf,1,ttputc);
#else
		printf("%s\n",start);
#endif
		start = s;
		*s = cs;
	}
	Lookahead = start;
	return(lim);
}

static
void
save2_article(tempfname)
char *tempfname;
{
	FILE *in, *out;
	int c;
	time_t timenow, time();
	char *today, *ctime();

	if ((in = fopen(tempfname, "r")) == NULL)
		return;
	if ((out = fopen(Ccfile, "a")) == NULL)
	{
	    fclose(in);
	    return;
	}
	timenow = time((time_t)0);
	today = ctime(&timenow);
	fprintf(out,"From vn %s",today);
	while ((c=getc(in)) != EOF)
		putc(c, out);
	putc('\n', out);
	fclose(in);
	fclose(out);
#ifdef amiga
	sprintf (pr_buf,"a copy has been saved in %s\n", Ccfile);
	tputs(pr_buf,1,ttputc);
#else
	printf ("a copy has been saved in %s\n", Ccfile);
#endif
}

/*
	send article to printer
*/
static
void
printr ()
{
	char cmd[RECLEN];
	char fn[L_tmpnam];
	long savepos;
	FILE *f;

	tmpnam (fn);
	if ((f = fopen(fn,"w")) == NULL)
	{
		rerrmsg("can't open temp file, %s",fn);
		return;
	}

	savepos = ftell(Fpread);
	rewind(Fpread);
	while (fgets(cmd,RECLEN-1,Fpread) != NULL)
		fputs(cmd,f);
	fclose(f);
	fseek(Fpread,savepos,0);

	tty_set (SAVEMODE);
#ifdef amiga
	sprintf(pr_buf,"Sent to printer\n");
	tputs(pr_buf,1,ttputc);
#else
	printf("Sent to printer\n");
#endif
#ifdef amiga
	sprintf (cmd,"%s >null: %s",Printer,fn);
#else
	sprintf (cmd,"%s %s 2>/dev/null",Printer,fn);
#endif
	system (cmd);
	tty_set (RESTORE);
	unlink (fn);
}

/*
	search article for specified search pattern, returning the line on which
		it is found in buf, a null buffer otherwise. The input file will
		be positioned either after the line on which the pattern is
		found, or unaaltered if match fails.
*/
static
void
searcher (buf)
char	*buf;
{
	static char	searchstr[RECLEN] = "";
	char	lasave[RECLEN];
	char	*reg, *regcmp(), *regex();
	long	current;
	int	orlines;

	/* save position, then request search pattern */
	current = ftell(Fpread);
	orlines = Rlines;

	sprintf (lasave,SEARCHFORM,searchstr);
	user_str (searchstr,lasave,0,searchstr);

	/* Now compile the search string */
	if(( reg = regcmp(searchstr, (char *)0)) == NULL) {
		rerrmsg("Invalid search string \"%s\"", searchstr);
		*buf = '\0';
		return;
	}

	/* try lookahead buffer first */
	if (Lookahead != NULL && regex(reg,Lookahead) != NULL)
	{
		strcpy(buf,Lookahead);
		regfree(reg);
		return;
	}

	/* Lookahead can point into buf */
	if (Lookahead != NULL)
		strcpy(lasave,Lookahead);

	/* now start reading lines, rotating if necessary and do search */
	while (fgets(buf,RECLEN-1,Fpread) != NULL)
	{
		if (Rot != 0 && Rlines >= Hlines)
			rot_line(buf);
		++Rlines;
		if( regex(reg, buf) != NULL ){	/* Got it */
#ifdef	MINIX
/* inverse video mucks up this line somehow */
			rerrmsg("\tSkipping ...\n\n");
#else
			rerrmsg("\n\tSkipping ....\n\n");
#endif
			regfree(reg);
			return;
		}
	}

	/* no dice, so restore position */
	regfree(reg);
	rerrmsg("Cannot find string \"%s\" in remainder of article", searchstr);
	fseek(Fpread,current,0);
	Rlines = orlines;
	if (Lookahead != NULL)
		strcpy(buf,lasave);
	else
		*buf = '\0';
}

/*
** print a reverse video error message while reading an article.
*/
static
rerrmsg(s,a,b,c,d,e)
char *s;
long a,b,c,d,e;
{
	term_set (ONREVERSE);
#ifdef amiga
	sprintf(pr_buf,"\n ");
	tputs(pr_buf,1,ttputc);
	sprintf(pr_buf,s,a,b,c,d,e);
	tputs(pr_buf,1,ttputc);
	sprintf(pr_buf," \07\n");
	tputs(pr_buf,1,ttputc);
#else
	printf("\n ");
	printf(s,a,b,c,d,e);
	printf(" \07\n");
#endif
	term_set (OFFREVERSE);
}
