/* $Id: bits.c,v 3.0 1992/02/01 03:09:32 davison Trn $
 */
/* 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 "EXTERN.h"
#include "common.h"
#include "cache.h"
#include "INTERN.h"
#include "bits.h"
#include "EXTERN.h"
#include "rcstuff.h"
#include "head.h"
#include "term.h"
#include "util.h"
#include "util2.h"
#include "final.h"
#include "trn.h"
#include "ng.h"
#include "artio.h"
#include "intrp.h"
#include "ngdata.h"
#include "rcln.h"
#include "ndir.h"
#include "nntp.h"
#include "rthread.h"
#include "rt-select.h"
#include "rt-util.h"

#ifdef DBM_XREFS
#    ifdef NULL
#	undef NULL
#    endif
#    include <dbm.h>
#endif

static long chase_count = 0;

void
bits_init()
{
    ;
}

void
rc_to_bits()
{
    char *mybuf = buf;			/* place to decode rc line */
    register char *s, *c, *h;
    register long i;
    register ART_NUM unread;
    register ARTICLE *ap;

    /* modify the article flags to reflect what has already been read */

    for (s = rcline[ng] + rcnums[ng]; *s == ' '; s++) ;
					/* find numbers in rc line */
    i = strlen(s);
#ifndef lint
    if (i >= LBUFLEN-2)			/* bigger than buf? */
	mybuf = safemalloc((MEM_SIZE)(i+2));
#endif
    strcpy(mybuf,s);			/* make scratch copy of line */
    mybuf[i++] = ',';			/* put extra comma on the end */
    mybuf[i] = '\0';
    s = mybuf;				/* initialize the for loop below */
    if (strnEQ(s,"1-",2)) {		/* can we save some time here? */
	firstart = atol(s+2)+1;		/* process first range thusly */
	if (firstart < absfirst)
	    firstart = absfirst;
	s=index(s,',') + 1;
	for (i = absfirst, ap = article_ptr(i); i < firstart; i++, ap++)
	    ap->flags |= AF_READ;
    } else {
	i = firstart = absfirst;
	ap = article_ptr(i);
    }
    unread = lastart - firstart + 1;	/* assume this range unread */
#ifdef DEBUG
    if (debug & DEB_CTLAREA_BITMAP) {
	printf("\n%s\n",mybuf) FLUSH;
	for (i = absfirst, ap = article_ptr(i); i < firstart; i++, ap++)
	    if (!(ap->flags & AF_READ))
		printf("%ld ",(long)i) FLUSH;
    }
#endif
    for ( ; (c = index(s,',')) != Nullch; s = ++c) {	/* for each range */
	ART_NUM min, max;

	*c = '\0';			/* do not let index see past comma */
	h = index(s,'-');
	min = atol(s);
	if (h)				/* is there a dash? */
	    max = atol(h+1);
	else
	    max = min;
	if (min < firstart)		/* make sure range is in range */
	    min = firstart;
	if (min > lastart)
	    min = lastart+1;
	for (; i < min; i++, ap++)
	    ap->flags &= ~AF_READ;
	if (max > lastart)
	    max = lastart;
	if (min <= max) {		/* non-null range? */
	    unread -= max - min + 1;/* adjust unread count */
	    /* mark all arts in range as read */
	    for (; i <= max; i++, ap++)
		ap->flags |= AF_READ;
	}
#ifdef DEBUG
	if (debug & DEB_CTLAREA_BITMAP) {
	    printf("\n%s\n",s) FLUSH;
	    for (i=absfirst; i <= lastart; i++)
		if (!was_read(i))
		    printf("%ld ",(long)i) FLUSH;
	}
#endif
    }
    for (; i <= lastart; i++, ap++)
	ap->flags &= ~AF_READ;
#ifdef DEBUG
    if (debug & DEB_CTLAREA_BITMAP) {
	fputs("\n(hit CR)",stdout) FLUSH;
	fgets(cmd_buf, sizeof cmd_buf, stdin);
    }
#endif
    if (mybuf != buf)
	free(mybuf);
    toread[ng] = unread;
}

/* reconstruct the .newsrc line in a human readable form */

void
bits_to_rc()
{
    register char *s, *mybuf = buf;
    register ART_NUM i;
    ART_NUM count=0;
    int safelen = LBUFLEN - 32;

    strcpy(buf,rcline[ng]);		/* start with the newsgroup name */
    s = buf + rcnums[ng] - 1;		/* use s for buffer pointer */
    *s++ = RCCHAR(rcchar[ng]);		/* put the requisite : or !*/
    for (i=absfirst; i<=lastart; i++)
	if (!was_read(i))
	    break;
    sprintf(s," 1-%ld,",(long)i-1);
    s += strlen(s);
    for (; i<=lastart; i++) {	/* for each article in newsgroup */
	if (s-mybuf > safelen) {	/* running out of room? */
	    safelen *= 2;
	    if (mybuf == buf) {		/* currently static? */
		*s = '\0';
		mybuf = safemalloc((MEM_SIZE)safelen + 32);
		strcpy(mybuf,buf);	/* so we must copy it */
		s = mybuf + (s-buf);
					/* fix the pointer, too */
	    }
	    else {			/* just grow in place, if possible */
		char *newbuf;

		newbuf = saferealloc(mybuf,(MEM_SIZE)safelen + 32);
		s = newbuf + (s-mybuf);
		mybuf = newbuf;
	    }
	}
	if (!was_read(i))		/* still unread? */
	    count++;			/* then count it */
	else {				/* article was read */
	    ART_NUM oldi;

	    sprintf(s,"%ld",(long)i);	/* put out the min of the range */
	    s += strlen(s);		/* keeping house */
	    oldi = i;			/* remember this spot */
	    do i++; while (i <= lastart && was_read(i));
					/* find 1st unread article or end */
	    i--;			/* backup to last read article */
	    if (i > oldi) {		/* range of more than 1? */
		sprintf(s,"-%ld,",(long)i);
					/* then it out as a range */
		s += strlen(s);		/* and housekeep */
	    }
	    else
		*s++ = ',';		/* otherwise, just a comma will do */
	}
    }
    if (*(s-1) == ',')			/* is there a final ','? */
	s--;				/* take it back */
    *s++ = '\0';			/* and terminate string */
#ifdef DEBUG
    if (debug & DEB_NEWSRC_LINE && !panic) {
	printf("%s: %s\n",rcline[ng],rcline[ng]+rcnums[ng]) FLUSH;
	printf("%s\n",mybuf) FLUSH;
    }
#endif
    free(rcline[ng]);			/* return old rc line */
    if (mybuf == buf) {
	rcline[ng] = safemalloc((MEM_SIZE)(s-buf)+1);
					/* grab a new rc line */
	strcpy(rcline[ng], buf);	/* and load it */
    }
    else {
	mybuf = saferealloc(mybuf,(MEM_SIZE)(s-mybuf)+1);
					/* be nice to the heap */
	rcline[ng] = mybuf;
    }
    *(rcline[ng] + rcnums[ng] - 1) = '\0';
    if (rcchar[ng] == NEGCHAR) {	/* did they unsubscribe? */
	printf(unsubto,ngname) FLUSH;
	toread[ng] = TR_UNSUB;		/* make line invisible */
    }
    else
	/*NOSTRICT*/
	toread[ng] = (ART_UNREAD)count;		/* remember how many unread there are */
}

#ifdef USE_NNTP

/* Parse the LISTGROUP output and set anything not mentioned as missing. */

void
setmissingbits()				/* NNTP version */
{
    register ART_NUM num, priornum;
    register ARTICLE *ap;

    if (!nntp_listgroup())
	return;
    for (priornum = absfirst-1, ap = article_ptr(absfirst);; ap++) {
	nntp_gets(ser_line, sizeof ser_line);
	if (NNTP_LIST_END(ser_line))
	    break;
	num = atol(ser_line);
	while (++priornum < num)
	    uncache_article(ap++,FALSE);
    }
}

#else /* !USE_NNTP */

/* Scan the directory to find which articles are present. */

void
setfoundbits()
{
    register ART_NUM first = lastart+1;
    register DIR *dirp;
    register Direntry_t *dp;
    long an;
    char ch;

    if (!(dirp = opendir(".")))
	return;
    
    found_min = absfirst;
    an = (lastart-found_min)/BITSPERBYTE+20;
    found_bits = safemalloc((MEM_SIZE)an);
    bzero(found_bits, an);

    while ((dp = readdir(dirp)) != Null(Direntry_t*)) {
	if (sscanf(dp->d_name, "%ld%c", &an, &ch) == 1) {
	    if (an <= lastart && an >= found_min) {
		if (an < first)
		    first = an;
		foundart(an);
	    }
	}
    }
    closedir(dirp);
    abs1st[ng] = first;
    if (first > absfirst)
	checkexpired(ng,first);
    absfirst = first;
}

void
setmissingbits()				/* non-NNTP version */
{
    register ARTICLE *ap;
    register ART_NUM an;

    if (!found_bits)
	return;

    for (an = absfirst, ap = article_ptr(an); an <= lastart; an++, ap++) {
	if (artismissing(an))
	    onemissing(ap);
    }
    free(found_bits);
    found_bits = NULL;
}
#endif /* !USE_NNTP */

/* mark an article unread, keeping track of toread[] */

void
onemore(ap)
ARTICLE *ap;
{
    if (ap->flags & AF_READ) {
	register ART_NUM artnum = article_num(ap);
	check_first(artnum);
	ap->flags &= ~AF_READ;
	++toread[ng];
	ap->flags &= ~AF_DEL;
	if (ap->subj) {
	    if (selected_only) {
		if (ap->subj->flags & sel_mask) {
		    ap->flags |= sel_mask;
		    selected_count++;
		}
	    } else
		ap->subj->flags |= SF_VISIT;
	}
    }
}

/* mark an article read, keeping track of toread[] */

void
oneless(ap)
ARTICLE *ap;
{
    if (!(ap->flags & AF_READ)) {
	ap->flags |= AF_READ;
	/* Keep selected_count accurate */
	if (ap->flags & sel_mask) {
	    selected_count--;
	    ap->flags &= ~sel_mask;
	}
	if (toread[ng] > TR_NONE)
	    --toread[ng];
    }
}

void
onemissing(ap)
ARTICLE *ap;
{
    oneless(ap);
    ap->flags = (ap->flags & ~(AF_HAS_RE|AF_YANKBACK|AF_FROMTRUNCED))
	      | AF_MISSING|AF_CACHED|AF_THREADED;
}

/* mark an article as unread, with possible xref chasing */

void
unmark_as_read()
{
    register ARTICLE *ap = article_ptr(art);
    onemore(ap);
#ifdef MCHASE
    if (ap->xrefs != nullstr && !(ap->flags & AF_MCHASE)) {
	ap->flags |= AF_MCHASE;
	chase_count++;
    }
#endif
}

/* Mark an article as read in this newsgroup and possibly chase xrefs.
** Don't call this on missing articles.
*/
void
set_read(ap)
register ARTICLE *ap;
{
    oneless(ap);
    if (!olden_days && ap->xrefs != nullstr && !(ap->flags & AF_KCHASE)) {
	ap->flags |= AF_KCHASE;
	chase_count++;
    }
}

/* temporarily mark article as read.  When newsgroup is exited, articles */
/* will be marked as unread.  Called via M command */

void
delay_unmark(ap)
ARTICLE *ap;
{
    if (!(ap->flags & AF_YANKBACK)) {
	ap->flags |= AF_YANKBACK;
	dmcount++;
    }
}

/* mark article as read.  If article is cross referenced to other */
/* newsgroups, mark them read there also. */

void
mark_as_read()
{
    register ARTICLE *ap = article_ptr(art);
    oneless(ap);
    if (ap->xrefs != nullstr && !(ap->flags & AF_KCHASE)) {
	ap->flags |= AF_KCHASE;
	chase_count++;
    }
    checkcount++;			/* get more worried about crashes */
}

/* keep firstart pointing at the first unread article */

void
check_first(min)
ART_NUM min;
{
    if (min < absfirst)
	min = absfirst;
    if (min < firstart)
	firstart = min;
}

/* bring back articles marked with M */

void
yankback()
{
    register ARTICLE *ap;

    if (dmcount) {			/* delayed unmarks pending? */
	if (mode == 't')
	    sprintf(buf, "Returned %ld Marked article%s.",(long)dmcount,
		dmcount == 1 ? nullstr : "s");
	else
#ifdef VERBOSE
	    printf("\nReturning %ld Marked article%s...\n",(long)dmcount,
		dmcount == 1 ? nullstr : "s") FLUSH;
#endif
	for (art=absfirst, ap=article_ptr(art); art <= lastart; art++, ap++) {
	    if ((ap->flags & (AF_YANKBACK|AF_MISSING)) == AF_YANKBACK) {
		unmark_as_read();
		if (selected_only)
		    select_article(ap, 0);
		ap->flags &= ~AF_YANKBACK;
	    }
	}
	dmcount = 0;
    }
}

static int chase_xref _((ART_NUM,int));

int
chase_xrefs(until_key)
bool_int until_key;
{
    register ARTICLE *ap;
    register ART_NUM an;

    if (!chase_count)
	return 1;
    if (until_key)
	setspin(SPIN_BACKGROUND);

    for (an = absfirst, ap = article_ptr(an); an <= lastart; an++, ap++) {
	if (ap->flags & AF_KCHASE) {
	    chase_xref(an,TRUE);
	    ap->flags &= ~AF_KCHASE;
	    if (!--chase_count)
		break;
	}
#ifdef MCHASE
	if (ap->flags & AF_MCHASE) {
	    chase_xref(an,TRUE);
	    ap->flags &= ~AF_MCHASE;
	    if (!--chase_count)
		break;
	}
#endif
	if (until_key && input_pending())
	    return 0;
    }
    chase_count = 0;
    return 1;
}

/* run down xref list and mark as read or unread */

#ifndef DBM_XREFS
static int
chase_xref(artnum,markread)	/* The Xref-line-using version */
ART_NUM artnum;
int markread;
{
# ifdef VALIDATE_XREF_SITE
    bool valid_xref_site();
# endif
    register char *xartnum;
    register ART_NUM x;
    char *xref_buf, *curxref;
    char tmpbuf[128];

    if (inbackground())
	spin(10);
    else {
	if (output_chase_phrase) {
#ifdef VERBOSE
	    IF(verbose)
		fputs("\nChasing xrefs", stdout);
	    ELSE
#endif
#ifdef TERSE
		fputs("\nXrefs", stdout);
#endif
	    output_chase_phrase = 0;
	}
	putchar('.'), fflush(stdout);
    }

    xref_buf = fetchcache(artnum, XREF_LINE, FILL_CACHE);
    if (!xref_buf || !*xref_buf)
	return 0;

    xref_buf = savestr(xref_buf);
# ifdef DEBUG
    if (debug & DEB_XREF_MARKER)
	printf("Xref: %s\n",xref_buf) FLUSH;
# endif
    curxref = cpytill(tmpbuf,xref_buf,' ') + 1;
# ifdef VALIDATE_XREF_SITE
    if (valid_xref_site(artnum,tmpbuf))
# endif
    {
	while (*curxref) {		/* for each newsgroup */
	    curxref = cpytill(tmpbuf,curxref,' ');
	    xartnum = index(tmpbuf,':');
	    if (!xartnum)
		break;
	    *xartnum++ = '\0';
	    if (!(x = atol(xartnum)))
		continue;
	    if (strEQ(tmpbuf,ngname)) {/* is this the current newsgroup? */
		if (x < absfirst || x > lastart)
		    continue;
		if (markread)
		    oneless(article_ptr(x)); /* take care of old C newses */
#ifdef MCHASE
		else
		    onemore(article_ptr(x));
#endif
	    } else {
		if (markread) {
		    if (addartnum(x,tmpbuf))
			break;
		}
# ifdef MCHASE
		else
		    subartnum(x,tmpbuf);
# endif
	    }
	    while (*curxref && isspace(*curxref))
		curxref++;
	}
    }
    free(xref_buf);
    return 0;
}

# ifdef VALIDATE_XREF_SITE
/* Make sure the site name on Xref matches what inews thinks the site
 * is.  Check first against last inews_site.  If it matches, fine.
 * If not, fetch inews_site from current Path or Relay-Version line and
 * check again.  This is so that if the new administrator decides
 * to change the system name as known to inews, rn will still do
 * Xrefs correctly--each article need only match itself to be valid.
 */ 
bool
valid_xref_site(artnum, site)
ART_NUM artnum;
char *site;
{
    static char *inews_site = Nullch;
    char *sitebuf, *s;

    if (inews_site && strEQ(site,inews_site))
	return TRUE;

    if (inews_site)
	free(inews_site);
#ifndef ANCIENT_NEWS
    /* Grab the site from the first component of the Path line */
    sitebuf = fetchlines(artnum,PATH_LINE);
    if ((s = index(sitebuf, '!')) != Nullch) {
	*s = '\0';
	inews_site = savestr(sitebuf);
    }
#else /* ANCIENT_NEWS */
    /* Grab the site from the Posting-Version line */
    sitebuf = fetchlines(artnum,RVER_LINE);
    if ((s = instr(sitebuf,"; site ",TRUE)) != Nullch) {
	char *t = index(s+7, '.');
	if (t)
	    *t = '\0';
	inews_site = savestr(s+7);
    }
#endif /* ANCIENT_NEWS */
    else
	inews_site = savestr(nullstr);
    free(sitebuf);

    if (strEQ(site,inews_site))
	return TRUE;

#ifdef DEBUG
    if (debug)
	printf("Xref not from %s--ignoring\n",inews_site) FLUSH;
#endif
    return FALSE;
}
# endif /* VALIDATE_XREF_SITE */

#else /* DBM_XREFS */

static int
chase_xref(artnum,markread)		/* The DBM version */
ART_NUM artnum;
int markread;
{
    datum lhs, rhs;
    datum fetch();
    register char *idp;
    char *ident_buf;
    static FILE *hist_file = Nullfp;
    long pos;
    register char *xartnum;
    register ART_NUM x;
    char *xref_buf, *curxref;
    char tmpbuf[128];

    if (inbackground())
	spin(10);
    else {
	if (output_chase_phrase) {
#ifdef VERBOSE
	    IF(verbose)
		fputs("\nChasing xrefs", stdout);
	    ELSE
#endif
#ifdef TERSE
		fputs("\nXrefs", stdout);
#endif
	    output_chase_phrase = 0;
	}
	putchar('.'), fflush(stdout);
    }

    xref_buf = fetchcache(artnum, NGS_LINE, FILL_CACHE);
    if (!xref_buf || !*xref_buf)
	return 0;

    xref_buf = safemalloc((MEM_SIZE)BUFSIZ);
    if (hist_file == Nullfp) {	/* Init. file accesses */
#ifdef DEBUG
	if (debug)
	    printf("chase_xref: opening files\n");
#endif
	dbminit(filexp(ARTFILE));
	if ((hist_file = fopen(filexp(ARTFILE), "r")) == Nullfp)
	    return 0;
    }
    ident_buf = fetchlines(artnum,MESSID_LINE);	/* get Message-ID */
#ifdef DEBUG
    if (debug)
	printf ("chase_xref: Message-ID: %s\n", ident_buf);
#endif
    
    if ((idp = index(ident_buf, '@')) != Nullch) {
	while (*++idp)			/* make message-id case insensitive */
	    if (isupper(*idp))
		*idp = tolower(*idp);
    }
    lhs.dptr = ident_buf;		/* look up article by id */
    lhs.dsize = strlen(lhs.dptr) + 1;
    rhs = fetch(lhs);			/* fetch the record */
    if (rhs.dptr == NULL)		/* if null, nothing there */
	goto wild_goose;
    bcopy(rhs.dptr,(char*)&pos, 4);
    fseek(hist_file, pos, 0);	/* datum returned is position in hist file */
    fgets(xref_buf, BUFSIZ, hist_file);
#ifdef DEBUG
    if (debug)
	printf ("Xref from history: %s\n", xref_buf);
#endif
    curxref = cpytill(tmpbuf, xref_buf, '\t') + 1;
    curxref = cpytill(tmpbuf, curxref, '\t') + 1;
#ifdef DEBUG
    if (debug)
	printf ("chase_xref: curxref: %s\n", curxref);
#endif
    while (*curxref) {			/* for each newsgroup */
	curxref = cpytill(tmpbuf,curxref,' ');
	xartnum = index(tmpbuf,'/');
	if (!xartnum)			/* probably an old-style Xref */
	    break;
	*xartnum++ = '\0';
	if (!(x = atol(xartnum)))
	    continue;
	if (strNE(tmpbuf,ngname)) {	/* not the current newsgroup? */
	    if (markread) {
		if (addartnum(x,tmpbuf))
		    goto wild_goose;
	    }
#ifdef MCHASE
	    else
		subartnum(x,tmpbuf);
#endif
	}
	while (*curxref && isspace(*curxref))
	    curxref++;
    }
  wild_goose:
    free(xref_buf);
    free(ident_buf);
    return 0;
}
#endif /* DBM_XREFS */
