/*  trn -- threaded readnews program based on rn 4.4
 *
 *  Author/Maintainer of trn: davison@borland.com (Wayne Davison)
 *  Organization: Borland International
 *  Author/Maintainer of rn: sob@bcm.tmc.edu (Stan Barber)
 *  Organization: Baylor College of Medicine, Houston,Tx
 *  Original Author: lwall@sdcrdcf.UUCP (Larry Wall)
 *  Organization: System Development Corporation, Santa Monica
 *
 *  History:
 *	01/14/83 - rn begun
 *	04/08/83 - rn 1.0
 *	09/01/83 - rn 2.0
 *	05/01/85 - rn 4.3
 *	11/01/89 - rn/rrn integration
 *	11/25/89 - trn begun
 *	07/21/90 - trn 1.0
 *	07/04/91 - rn 4.4
 *	11/25/91 - trn 2.0
 *	03/01/93 - trn 3.0 (or so... :-) )
 */
/* This software is Copyright 1991 by Stan Barber. 
 *
 * Permission is hereby granted to copy, reproduce, redistribute or otherwise
 * use this software as long as: there is no monetary profit gained
 * specifically from the use or reproduction of this software, it is not
 * sold, rented, traded or otherwise marketed, and this copyright notice is
 * included prominently in any copy made. 
 *
 * The authors make no claims as to the fitness or correctness of this software
 * for any use whatsoever, and it is provided as is. Any use of this software
 * is at the user's own risk. 
 */

#include "patchlevel.h"
static char rnid[] = "@(#)$Id: trn.c,v 3.0 1991/11/22 00:14:59 davison Trn $";
static char patchlevel[] = PATCHLEVEL;

#include "INTERN.h"
#include "common.h"
#include "trn.h"
#include "EXTERN.h"
#include "rcstuff.h"
#include "term.h"
#include "final.h"
#include "search.h"
#include "ngdata.h"
#include "ngstuff.h"
#include "util.h"
#include "util2.h"
#include "only.h"
#include "ngsrch.h"
#include "help.h"
#include "last.h"
#include "init.h"
#include "intrp.h"
#include "rcln.h"
#include "sw.h"
#include "cache.h"
#include "addng.h"
#include "ng.h"
#include "kfile.h"
#include "nntp.h"

void
rn_init()
{
    ;
}

void
main(argc,argv)
int argc;
char *argv[];
{
    bool foundany;
    register char *s;
    bool oh_for_the_good_old_days = FALSE;
    int direction = 1;

#if !THREAD_INIT
    /* Default to threaded operation if our name starts with a 't' */
    if ((s = rindex(argv[0],'/')) == Nullch)
	s = argv[0];
    else
	s++;
    if (*s == 't')
	use_threads = TRUE;
    else
	select_on = 0;
#endif
    foundany = initialize(argc,argv);

    if (maxngtodo)
	starthere = 0;
    else if (!foundany) {		/* nothing to do? */
#ifdef VERBOSE
	if (verbose)
	    fputs("\
No unread news in subscribed-to newsgroups.  To subscribe to a new\n\
newsgroup use the g<newsgroup> command.\n\
",stdout) FLUSH;
#endif
	starthere = nextrcline;
    }

    /* loop through all unread news */

    {
	bool special = FALSE;		/* temporarily allow newsgroup */
					/*   with no unread news? */
	bool retry;			/* cycle back to top of list? */
	NG_NUM recent_ng = 0;
	
	current_ng = 0;
	for (;;) {
	    retry = FALSE;
	    if (findlast) {
		findlast = FALSE;
		starthere = 0;
		if (*lastngname) {
		    if ((ng = find_ng(lastngname)) == nextrcline)
			ng = 0;
		    else {
			set_ngname(lastngname);
		    	set_toread(ng);
			if (toread[ng] <= TR_NONE)
			    ng = 0;
		    }
		}
	    } else {
		ng = starthere;
		starthere = 0;
	    }
	    while (ng <= nextrcline) {	/* for each newsgroup */
		if (ng == nextrcline) {	/* after the last newsgroup? */
		    mode = 'f';
#ifdef ONLY
		    if (maxngtodo) {
			if (retry)
#ifdef VERBOSE
			    IF(verbose)
				printf("\nRestriction %s%s still in effect.\n",
				    ngtodo[0],
				    maxngtodo > 1 ? ", etc." : nullstr) FLUSH;
			    ELSE
#endif
#ifdef TERSE
				fputs("\n(\"Only\" mode.)\n",stdout) FLUSH;
#endif
			else {
#ifdef VERBOSE
			    IF(verbose)
				fputs("\nNo articles under restriction.",
				  stdout) FLUSH;
			    ELSE
#endif
#ifdef TERSE
				fputs("\nNo \"only\" articles.",stdout) FLUSH;
#endif
			    end_only();	/* release the restriction */
			    retry = TRUE;
			}
		    }
#endif
		} else {
		    bool shoe_fits;	/* newsgroup matches restriction? */

		    mode = 'n';
		    if (toread[ng] >= TR_NONE) {	/* recalc toread? */
			set_ngname(rcline[ng]);
			shoe_fits = inlist(ngname);
			if (shoe_fits)
			    set_toread(ng);
			if (paranoid) {
			    recent_ng = current_ng;
			    current_ng = ng;
			    cleanup_rc();
					/* this may move newsgroups around */
			    ng = current_ng;
			    set_ngname(rcline[ng]);
			}
		    } else
			shoe_fits = TRUE;
		    if (toread[ng] < (emptyOnly || special ? TR_NONE : TR_ONE)
		     || !shoe_fits) {		/* unwanted newsgroup? */
			ng += direction;	/* then skip it */
			if (ng < 0) {
			   ng = 1;
			   direction = 1;
			}
			continue;
		    }
		}
		special = FALSE;	/* go back to normal mode */
		if (ng != current_ng) {
		    recent_ng = current_ng;
					/* remember previous newsgroup */
		    current_ng = ng;	/* remember current newsgroup */
		}
    reask_newsgroup:
		unflush_output();	/* disable any ^O in effect */
		if (ng >= nextrcline) {
		    dfltcmd = (retry ? "npq" : "qnp");
#ifdef VERBOSE
		    IF(verbose)
			printf("\n****** End of newsgroups -- what next? [%s] ",
			    dfltcmd);
		    ELSE
#endif
#ifdef TERSE
			printf("\n**** End -- next? [%s] ", dfltcmd);
#endif
		} else {
		    ThreadedGroup = (use_threads && rcchar[ng] != '0');
		    dfltcmd = (select_on
			&& (ART_NUM)toread[ng] >= select_on ? "+ynq" : "ynq");
#ifdef VERBOSE
		    IF(verbose)
			printf("\n%s %3ld unread article%s in %s -- read now? [%s] ",
			    ThreadedGroup? "======" : "******",
			    (long)toread[ng], (toread[ng]==TR_ONE ? nullstr : "s"),
			    ngname, dfltcmd);
		    ELSE
#endif
#ifdef TERSE
			printf("\n%s %3ld in %s -- read? [%s] ",
			    ThreadedGroup? "====" : "****",
			    (long)toread[ng],ngname,dfltcmd);
#endif
		}
		fflush(stdout);
    reinp_newsgroup:
		eat_typeahead();
		getcmd(buf);
		if (errno || *buf == '\f') {
		    putchar('\n') FLUSH; /* if return from stop signal */
		    goto reask_newsgroup;	/* give them a prompt again */
		}
		setdef(buf,dfltcmd);
#ifdef VERIFY
		printcmd();
#endif
    do_command:
		direction = 1;		/* default to forward motion */
		addnewbydefault = 0;
		switch (*buf) {
		case 'P':		/* goto previous newsgroup */
		    special = TRUE;	/* don't skip it if toread==0 */
		    /* FALL THROUGH */
		case 'p':		/* find previous unread newsgroup */
		    if (ng > 0)
			ng--;
		    direction = -1;	/* go backward in the newsrc */
		    break;
		case '-':
		    ng = recent_ng;	/* recall previous newsgroup */
		    if (ng < nextrcline)
			if (!get_ng(rcline[ng],0))
			    ng = current_ng;
		    special = TRUE;	/* don't skip it if toread==0 */
		    break;
		case 'x':
		    putchar('\n');
		    in_char("Confirm: exit and abandon .newsrc changes?",
			    'A', "yn");
		    putchar('\n') FLUSH;
		    if (*buf != 'y')
			break;
		    oh_for_the_good_old_days = TRUE;
		    printf("\nMoving changed version to '.newnewsrc'.");
		    /* FALL THROUGH */
		case 'q': case 'Q':	/* quit? */
		    putchar('\n') FLUSH;
		    ng = nextrcline+1;	/* satisfy */
		    retry = FALSE;	/*   loop conditions */
		    break;
		case '^':
		    putchar('\n') FLUSH;
		    ng = 0;
		    break;
		case 'n':		/* find next unread newsgroup */
		    if (ng == nextrcline) {
			putchar('\n') FLUSH;
			retry = TRUE;
		    } else if (toread[ng] > TR_NONE)
			retry = TRUE;
		    ng++;
		    break;
		case 'N':		/* goto next newsgroup */
		    special = TRUE;	/* and don't skip it if toread==0 */
		    ng++;
		    break;
		case '1':		/* goto 1st newsgroup */
		    ng = 0;
		    special = TRUE;	/* and don't skip it if toread==0 */
		    break;
		case '$':
		    ng = nextrcline;	/* goto last newsgroup */
		    retry = TRUE;
		    break;
		case 'L':
		    list_newsgroups();
		    goto reask_newsgroup;
		case '/': case '?':	/* scan for newsgroup pattern */
#ifdef NGSEARCH
		    switch (ng_search(buf,TRUE)) {
		    case NGS_ERROR:
			goto reask_newsgroup;
		    case NGS_ABORT:
			goto reinp_newsgroup;
		    case NGS_INTR:
#ifdef VERBOSE
			IF(verbose)
			    fputs("\n(Interrupted)\n",stdout) FLUSH;
			ELSE
#endif
#ifdef TERSE
			    fputs("\n(Intr)\n",stdout) FLUSH;
#endif
			ng = current_ng;
			goto reask_newsgroup;
		    case NGS_FOUND:
			special = TRUE;	/* don't skip it if toread==0 */
			break;
		    case NGS_NOTFOUND:
#ifdef VERBOSE
			IF(verbose)
			    fputs("\n\nNot found -- use a or g to add newsgroups\n",
				stdout) FLUSH;
			ELSE
#endif
#ifdef TERSE
			    fputs("\n\nNot found\n",stdout) FLUSH;
#endif
			goto reask_newsgroup;
		    }
#else
		    notincl("/");
#endif
		    break;
		case 'm':
#ifndef RELOCATE
		    notincl("m");
		    break;
#endif		    
		case 'g':	/* goto named newsgroup */
		    if (!finish_command(FALSE))
					/* if they didn't finish command */
			goto reinp_newsgroup;	/* go try something else */
		    for (s = buf+1; *s == ' '; s++);
					/* skip leading spaces */
#ifdef RELOCATE
		    if (!*s && *buf == 'm' && ngname && ng < nextrcline)
			strcpy(s,ngname);
#endif
		    {
		        char *_s;
			for (_s=s; isdigit(*_s); _s++)
			    ;
			if (isspace(*_s))
			    *_s = '\0';
		        if (*_s)
			    /* found non-digit before hitting end */
			    set_ngname(s);
			else {
			    int rcnum;
			    rcnum = atoi(s);
			    if (rcnum < nextrcline)
				set_ngname(rcline[rcnum]);
			    else {
				printf("\nOnly %d groups. Try again.\n",
					nextrcline) FLUSH;
				goto reask_newsgroup;
			    }
			}
		    }
		    /* try to find newsgroup */
#ifdef RELOCATE
		    if (!get_ng(ngname,(*buf=='m'?GNG_RELOC:0) | GNG_FUZZY))
#else
		    if (!get_ng(ngname,GNG_FUZZY))
#endif
			ng = current_ng;/* if not found, go nowhere */
		    special = TRUE;	/* don't skip it if toread==0 */
		    break;
#ifdef DEBUG
		case 'D':
		    printf("\nTries: %d Hits: %d\n",
			softtries,softtries-softmisses) FLUSH;
		    goto reask_newsgroup;
#endif
		case '!':		/* shell escape */
		    if (escapade())	 /* do command */
			goto reinp_newsgroup;
					/* if rubbed out, re input */
		    goto reask_newsgroup;
		case Ctl('k'):		/* edit global KILL file */
		    edit_kfile();
		    goto reask_newsgroup;
		case 'c':		/* catch up */
#ifdef CATCHUP
		    if (ng < nextrcline)
			ask_catchup();
		    if (toread[ng] == TR_NONE)
			ng++;
#else
		    notincl("c");
#endif
		    break;
		case 't':
		    if (!use_threads)
			printf("\n\nNot running in thread mode.\n");
		    else if (ng < nextrcline && toread[ng] >= TR_NONE) {
			bool read_unthreaded = (rcchar[ng] == ':');
			rcchar[ng] = (read_unthreaded ? '0' : ':');
			printf("\n\n%s will be read %sthreaded.\n",
			    rcline[ng], read_unthreaded? "un" : "") FLUSH;
			set_toread(ng);
		    }
		    special = TRUE;	/* don't skip it if toread==0 */
		    break;
		case 'u':		/* unsubscribe */
		    if (ng < nextrcline && toread[ng] >= TR_NONE) {
					/* unsubscribable? */
			printf(unsubto,rcline[ng]) FLUSH;
			rcchar[ng] = NEGCHAR;
					/* unsubscribe to (from?) it */
			toread[ng] = TR_UNSUB;
					/* and make line invisible */
			ng++;		/* do an automatic 'n' */
		    }
		    break;
		case 'h': {		/* help */
		    int cmd;

		    if ((cmd = help_ng()) > 0)
			pushchar(cmd);
		    goto reask_newsgroup;
		}
		case 'A':
		    if (ng >= nextrcline)
			break;
reask_abandon:
#ifdef VERBOSE
		    IF(verbose)
			in_char("\nAbandon changes to current newsgroup?",
				'B', "yn");
		    ELSE
#endif
#ifdef TERSE
			in_char("\nAbandon?", 'B', "ynh");
#endif
#ifdef VERIFY
		    printcmd();
#endif
		    putchar('\n') FLUSH;
		    if (*buf == 'h') {
#ifdef VERBOSE
			printf("Type y or SP to abandon the changes to this group since you started trn.\n");
			printf("Type n to leave the group as it is.\n");
#else
			printf("y or SP to abandon changes to this group.\n");
			printf("n to forget it.\n");
#endif
			goto reask_abandon;
		    } else if (*buf != 'y' && *buf != 'n' && *buf != 'q') {
			printf(hforhelp);
			settle_down();
			goto reask_abandon;
		    } else if (*buf == 'y')
			abandon_ng(ng);
		    special = TRUE;	/* don't skip it if toread==0 */
		    break;
		case 'a':
#ifndef FINDNEWNG
		    notincl("a");
		    goto reask_newsgroup;
#else
		    /* FALL THROUGH */
#endif
		case 'o':
		case 'O':
#ifdef ONLY
		{
#ifdef FINDNEWNG
		    bool doscan = (*buf == 'a');
#endif

		    if (!finish_command(TRUE)) /* get rest of command */
			goto reinp_newsgroup;	/* if rubbed out, try something else */
		    end_only();
		    if (buf[1]) {
			bool minusd = instr(buf+1,"-d", TRUE) != Nullch;

			sw_list(buf+1);
			if (minusd)
			    cwd_check();
			putchar('\n') FLUSH;
#ifdef FINDNEWNG
			if (doscan && maxngtodo)
			    scanactive();
#endif
			emptyOnly = (*buf == 'o' && maxngtodo);
		    }
		    ng = 0;		/* simulate ^ */
		    retry = FALSE;
		    break;
		}
#else
		    notincl("o");
		    goto reask_newsgroup;
#endif
		case '&':
		    if (switcheroo()) /* get rest of command */
			goto reinp_newsgroup;	/* if rubbed out, try something else */
		    goto reask_newsgroup;
		case 'l': {		/* list other newsgroups */
		    if (!finish_command(TRUE)) /* get rest of command */
			goto reinp_newsgroup;	/* if rubbed out, try something else */
		    for (s = buf+1; *s == ' '; s++);
		    			/* skip leading spaces */
		    sprintf(cmd_buf,"%s '%s'",filexp(NEWSGROUPS),s);
		    resetty();
		    if (doshell(sh,cmd_buf))
#ifdef VERBOSE
			IF(verbose)
			    fputs("    (Error from newsgroups program)\n",
				stdout) FLUSH;
			ELSE
#endif
#ifdef TERSE
			    fputs("(Error)\n",stdout) FLUSH;
#endif
		    noecho();
		    crmode();
		    goto reask_newsgroup;
		}
		case 'U': case '+':
		case '.': case '=':
		case 'y': case 'Y': case '\t': /* do normal thing */
		    if (ng >= nextrcline) {
			fputs("\nNot on a newsgroup.",stdout) FLUSH;
			goto reask_newsgroup;
		    } else if (*buf == '.') {	/* start command? */
			if (!finish_command(FALSE)) /* get rest of command */
			    goto reinp_newsgroup;
			s = savestr(buf+1);  /* do_newsgroup will free it */
		    } else {
			s = buf;
			if (*buf == '+' || *buf == 'U' || *buf == '=')
			    *s++ = lastchar; /* restore 0200 if from a macro */
			save_typeahead(s, sizeof buf - 1);
			if (*buf)
			    s = savestr(buf);
			else
			    s = Nullch;
		    }
		    if (toread[ng])
			retry = TRUE;
		    switch (do_newsgroup(s)) {
		    case NG_ERROR:
		    case NG_NORM:
			ng++;
			break;
		    case NG_ASK:
			goto reask_newsgroup;
		    case NG_SELPRIOR:
			*buf = 'p';
			goto do_command;
		    case NG_SELNEXT:
			*buf = 'n';
			goto do_command;
		    case NG_MINUS:
			ng = recent_ng;	/* recall previous newsgroup */
			special = TRUE;	/* don't skip it if toread==0 */
			break;
		    }
		    break;
#ifdef STRICTCR
		case '\n':
		    fputs(badcr,stdout) FLUSH;
		    goto reask_newsgroup;
#endif
		case 'v':
		    putchar('\n');
		    trn_version();
		    goto reask_newsgroup;
		default:
		    printf("\n%s",hforhelp) FLUSH;
		    settle_down();
		    goto reask_newsgroup;
		}
	    }
	    if (!retry)
		break;
#ifdef USE_NNTP
	    if (actfp && actFetchTime
	     && time(Null(time_t*)) - lastactfetch > actFetchTime) {
		fclose(actfp);
		printf("Grabbing new active file..."), fflush(stdout);
		ngdata_init();	/* re-grab the active file */
		carriage_return();
		erase_eol();
	    }
#endif
	}
    }

    /* now write .newsrc back out */

    write_rc();

    if (oh_for_the_good_old_days)
	get_old_rc();

    finalize(0);			/* and exit */
}

void
trn_version()
{
    printf("\nTrn version:%s.\nConfigured for ",patchlevel,rnid);
#ifdef USE_NNTP
    printf("NNTP news access.\n");
#else
    printf("Local news access.\n");
#endif
    printf("Send bug reports, suggestions, etc. to: davison@borland.com\n") FLUSH;
}

/* set current newsgroup */

void
set_ngname(what)
char *what;
{
    int len = strlen(what)+1;

    growstr(&ngname,&ngnlen,len);
    strcpy(ngname,what);
    growstr(&ngdir,&ngdlen,len);
    strcpy(ngdir,getngdir(ngname));
}

static char *myngdir;
static int ngdirlen = 0;

char *
getngdir(ngnam)
char *ngnam;
{
    register char *s;

    growstr(&myngdir,&ngdirlen,strlen(ngnam)+1);
    strcpy(myngdir,ngnam);
    for (s = myngdir; *s; s++)
	if (*s == '.')
	    *s = '/';
    return myngdir;
}
