/*
 *	(c) Copyright 1990, Kim Fabricius Storm.  All rights reserved.
 *
 *	Basic article access and management
 */

#include "config.h"
#include "db.h"
#include "articles.h"
#include "regexp.h"
#ifdef ART_GREP
#include "news.h"
#endif /* ART_GREP */

/* articles.c */

static void new_thunk __APROTO((thunk *t, char *ptr, long size));
#ifdef ART_GREP
static int grep_article __APROTO((group_header *gh, article_header *ah, char *pattern, int (*fcn)()));
#endif /* ART_GREP */

export int  seq_cross_filtering = 1;
export int  select_leave_next = 0; 	/* ask to select left over art. */
export int  ignore_re = 0;
export int  body_search_header = 0;
export int  cross_post_limit = 0;

import int  ignore_fancy_select;
import int  killed_articles;

extern attr_type test_article();

/*
 * memory management
 */

static thunk
    dummy_str_t = {
	NULL,
	NULL,
	0L
    },
    dummy_art_t = {
	NULL,
	NULL,
	0L
    };


static thunk *first_str_t = &dummy_str_t;
static thunk *current_str_t = &dummy_str_t;
static thunk *first_art_t = &dummy_art_t;
static thunk *current_art_t = &dummy_art_t;
static long  cur_str_size = 0, cur_art_size = 0;
static char *next_str;
static article_header *next_art;
static article_header **art_array = NULL;

static article_number max_articles = 0, mem_offset = 0;

/*
 * allocate one article header
 */

#ifndef ART_THUNK_SIZE
#define ART_THUNK_SIZE	127
#endif

static void
new_thunk(t, ptr, size)
thunk *t;
char *ptr;
long size;
{
    thunk *new;

    new = newobj(thunk, 1);

    new->next_thunk = t->next_thunk;
    t->next_thunk = new;

    new->this_thunk = ptr;
    new->thunk_size = size;
}


article_header *alloc_art()
{
    if (cur_art_size == 0) {
	if (current_art_t->next_thunk == NULL)
	    new_thunk(current_art_t,
		      (char *)newobj(article_header, ART_THUNK_SIZE),
		      (long)ART_THUNK_SIZE);

	current_art_t = current_art_t->next_thunk;
	next_art = (article_header *)current_art_t->this_thunk;
	cur_art_size = current_art_t->thunk_size;
    }

    cur_art_size--;
    return next_art++;
}

/*
 * allocate a string of length 'len'
 */

#ifndef STR_THUNK_SIZE
#define STR_THUNK_SIZE	((1<<14) - 32)	/* leave room for malloc header */
#endif

char *alloc_str(len)
int len;
{
    /* allow space for '\0', and align on word boundary */
    int size  = (len + sizeof(int)) & ~(sizeof(int) - 1);
    char *ret;

    if (cur_str_size < size) {
	if (current_str_t->next_thunk == NULL)
	    new_thunk(current_str_t,
		      newstr(STR_THUNK_SIZE), (long)STR_THUNK_SIZE);

	current_str_t = current_str_t->next_thunk;
	next_str = current_str_t->this_thunk;
	cur_str_size = current_str_t->thunk_size;
    }
    /* XXX else should do something reasonable */

    ret = next_str;
    cur_str_size -= size;
    next_str += size;

    ret[len] = NUL;	/* ensure string is null terminated */
    return ret;
}

/*
 * "free" the allocated memory
 */

void
free_memory()
{
    current_str_t = first_str_t;
    current_art_t = first_art_t;
    cur_str_size  = 0;
    cur_art_size  = 0;
    n_articles	  = 0;
}


/*
 * mark/release memory
 */


void
mark_str(str_marker)
string_marker *str_marker;
{
    str_marker->sm_cur_t = current_str_t;
    str_marker->sm_size  = cur_str_size;
    str_marker->sm_next  = next_str;
}

void
release_str(str_marker)
string_marker *str_marker;
{
    current_str_t = str_marker->sm_cur_t;
    cur_str_size  = str_marker->sm_size;
    next_str      = str_marker->sm_next;
}


void
mark_memory(mem_marker)
memory_marker *mem_marker;
{
    mark_str(&(mem_marker->mm_string));

    mem_marker->mm_cur_t = current_art_t;
    mem_marker->mm_size  = cur_art_size;
    mem_marker->mm_next  = next_art;

    mem_marker->mm_nart	 = n_articles;
    mem_offset += n_articles;

    n_articles = 0;
    articles = art_array + mem_offset;
}

void
release_memory(mem_marker)
memory_marker *mem_marker;
{
    release_str(&(mem_marker->mm_string));

    current_art_t = mem_marker->mm_cur_t;
    cur_art_size  = mem_marker->mm_size;
    next_art      = mem_marker->mm_next;

    n_articles = mem_marker->mm_nart;

    mem_offset -= n_articles;
    articles = art_array + mem_offset;
}

/*
 * merge all memory chunks into one.
 */

void 
merge_memory()
{
    n_articles += mem_offset;
    mem_offset = 0;
    articles = art_array;
}


/*
 * save article header in 'articles' array
 * 'articles' is enlarged if too small
 */

#define	FIRST_ART_ARRAY_SIZE	500	/* malloc header */
#define	NEXT_ART_ARRAY_SIZE	512

void
add_article(art)
article_header *art;
{
    if ((n_articles + mem_offset) == max_articles) {
	/* must increase size of 'articles' */

	if (max_articles == 0) {
	    /* allocate initial 'articles' array */
	    max_articles = FIRST_ART_ARRAY_SIZE;
	} else {
	    max_articles += NEXT_ART_ARRAY_SIZE;
	}
	art_array = resizeobj(art_array, article_header *, max_articles);
	articles = art_array + mem_offset;
    }

    articles[n_articles] = art;
    n_articles++;
}


int
access_group(gh, first_article, last_article, flags, mask)
register group_header *gh;
article_number first_article, last_article;
register flag_type flags;
char *mask;
{
    register article_header *ah;
    int skip_digest, n;
    group_header *cpgh;
#ifdef NOV
    int dbstatus;
#else /* NOV */
    FILE *data;
    off_t data_offset, data_size;
#endif /* NOV */
    cross_post_number cross_post;
    attr_type leave_attr;
    memory_marker mem_marker;
    static regexp *rexp = NULL;
    static char subptext[80];

    if (first_article < gh->first_db_article)
	first_article = gh->first_db_article;

    if (last_article > gh->last_db_article)
	last_article = gh->last_db_article;

    if (last_article == 0 || first_article > last_article) return 0;

#ifdef NOV
    db_read_group(gh, first_article, last_article);
#else
    data = open_data_file(gh, 'd', OPEN_READ);
    if (data == NULL) return -10;

    if ((data_offset = get_data_offset(gh, first_article)) == (off_t)(-1))
	return -11;
#endif /* NOV */

    if (mask == NULL) {
	if (rexp != NULL) {
	    freeobj(rexp);
	    rexp = NULL;
	}
    } else {
	if (*mask == '/') {
	    mask++;
	    if (rexp != NULL) {
		if (strncmp(mask, subptext, 80) != 0) {
		    freeobj(rexp);
		    rexp = NULL;
		}
	    }
	    if (rexp == NULL) {
		strncpy(subptext, mask, 80);
		rexp = regcomp(mask);
		if (rexp == NULL) return -1;
	    }
	    /* notice mask is still non-NULL */
	}
    }

    if ((flags & (ACC_ALSO_READ_ARTICLES | ACC_ONLY_READ_ARTICLES)))
	leave_attr = 0;
    else if (select_leave_next)
	leave_attr = A_READ;	/* will prompt */
    else
	leave_attr = A_LEAVE_NEXT;

    if (!(flags & ACC_SPEW_MODE))
	use_newsrc(gh, (flags & ACC_MERGED_NEWSRC) ? 2 :
		       (flags & ACC_ORIG_NEWSRC) ? 1 : 0);

    if ((flags & ACC_EXTRA_ARTICLES) == 0)
	mark_memory(&mem_marker);

    ah = alloc_art();

    skip_digest = 0;

#ifdef NOV
    /* XXX: db_read_art takes a FILE * in nnmaster version */
    while ((dbstatus = db_read_art((FILE *) -1)) > 0) {
#ifdef ART_GREP
	if (s_keyboard || s_hangup) break; /* add to allow interrupt of GREP */
#endif
        if (db_hdr.dh_number < first_article)
	    continue;
#else
    fseek(data, data_offset, 0);

    while (data_offset < gh->data_write_offset) {
#ifdef ART_GREP
	if (s_keyboard || s_hangup) break; /* add to allow interrupt of GREP */
#endif
	data_size = db_read_art(data);
	if (data_size <= 0) {
	    fclose(data);
	    if ((flags & ACC_EXTRA_ARTICLES) == 0)
		release_memory(&mem_marker);
	    return -2;
	}
	data_offset += data_size;
#endif /* NOV */
	if (db_hdr.dh_lpos == (off_t)0)
	    continue;	/* article not accessible */

	if (db_hdr.dh_number > gh->last_db_article
	    || db_hdr.dh_number < gh->first_db_article)
	    goto data_error;

	if (skip_digest && db_data.dh_type == DH_SUB_DIGEST)
	    continue;

	skip_digest = 0;

	if (cross_post_limit && db_hdr.dh_cross_postings >= cross_post_limit)
	    continue;

	if (db_hdr.dh_cross_postings && !(flags & ACC_ALSO_CROSS_POSTINGS)) {
	    for (n = 0; n < (int)db_hdr.dh_cross_postings; n++) {
		cross_post = NETW_CROSS_INT(db_data.dh_cross[n]);
		if (cross_post < 0 || cross_post >= master.number_of_groups)
		    continue;
		cpgh = ACTIVE_GROUP(cross_post);
		if (cpgh == gh) {
		    if (seq_cross_filtering) continue;
		    n = db_hdr.dh_cross_postings;
		    break;
		}
		if (!(flags & ACC_ALSO_UNSUB_GROUPS))
		    if (cpgh->group_flag & G_UNSUBSCRIBED) continue;

		if (!seq_cross_filtering) break;
		if (cpgh->preseq_index > 0 &&
		    cpgh->preseq_index < gh->preseq_index) break;
	    }

	    if (n < (int)db_hdr.dh_cross_postings) {
		if (db_data.dh_type == DH_DIGEST_HEADER) skip_digest++;
		continue;
	    }
	}

	ah->flag = 0;

	switch (db_data.dh_type) {
	 case DH_DIGEST_HEADER:
	    if (flags & ACC_DONT_SPLIT_DIGESTS)
		skip_digest++;
	    else
		if ((flags & ACC_ALSO_FULL_DIGEST) == 0)
		    continue;	/* don't want the full digest when split */
	    ah->flag |= A_FULL_DIGEST;
	    break;
	 case DH_SUB_DIGEST:
	    ah->flag |= A_DIGEST;
	    break;
	}

	ah->a_number = db_hdr.dh_number;
	if (ah->a_number > last_article) break;

	if (flags & ACC_SPEW_MODE) {
	    fold_string(db_data.dh_subject);
	    tprintf("%x:%s\n", (int)(gh->group_num), db_data.dh_subject);
	    continue;
	}

	ah->hpos = db_hdr.dh_hpos;
	ah->fpos = ah->hpos + (off_t)(db_hdr.dh_fpos);
	ah->lpos = db_hdr.dh_lpos;

	ah->attr = test_article(ah);

	if (flags & ACC_MERGED_NEWSRC) {
	    if (ah->attr == A_KILL) continue;
	} else if (ah->attr != A_READ && (flags & ACC_ONLY_READ_ARTICLES))
	    continue;

#ifdef ART_GREP
	if((flags & ACC_ON_GREP_UNREAD) && (ah->attr == A_READ))
	    continue;
#endif /* ART_GREP */

	if (rexp != NULL) {
	    if (flags & ACC_ON_SUBJECT)
		if (regexec_cf(rexp, db_data.dh_subject)) goto match_ok;
	    if (flags & ACC_ON_SENDER)
		if (regexec_cf(rexp, db_data.dh_sender)) goto match_ok;
#ifdef ART_GREP
	    if (flags & (ACC_ON_GREP_UNREAD | ACC_ON_GREP_ALL))
		if (grep_article(gh, ah, (char *)rexp, regexec_cf)) goto match_ok;
#endif /* ART_GREP */
	    continue;
	} else
	if (mask != NULL) {
	    if (flags & ACC_ON_SUBJECT)
		if (strmatch_cf(mask, db_data.dh_subject)) goto match_ok;
	    if (flags & ACC_ON_SENDER)
		if (strmatch_cf(mask, db_data.dh_sender)) goto match_ok;
#ifdef ART_GREP
	    if (flags & (ACC_ON_GREP_UNREAD | ACC_ON_GREP_ALL))
		if (grep_article(gh, ah, mask, strmatch_cf)) goto match_ok;
#endif /* ART_GREP */
	    continue;
	}
     match_ok:

     attr_again:

	switch (ah->attr) {
	 case A_LEAVE:
	    if (mask) {
		ah->attr = 0;
		break;
	    }

	    if (leave_attr == A_READ) {
		clrdisp();
		prompt("Select left over articles in group %s? ", gh->group_name);
		leave_attr = yes(0) > 0 ? A_SELECT : A_LEAVE_NEXT;
	    }
	    ah->attr = leave_attr;
	    goto attr_again;

	 case A_SELECT:
	    if (mask) ah->attr = 0;
	    break;

	 case A_READ:
	    if (flags & ACC_MERGED_NEWSRC) break;

	    if (!(flags & (ACC_ALSO_READ_ARTICLES | ACC_ONLY_READ_ARTICLES)))
		if (ah->a_number > gh->last_article)
		    continue;

	    /* FALL THRU */
	 case A_SEEN:
	 case 0:
	    if (flags & ACC_DO_KILL) {
		ah->replies = db_hdr.dh_replies;
		ah->sender = db_data.dh_sender;
		ah->subject = db_data.dh_subject;
		if (kill_article(ah)) continue;
	    }

	    /* The 'P' command to a read group must show articles as read */
             if (gh->unread_count <= 0)
               ah->attr = A_READ;
	   break;

	 default:
	    break;
	}

	if (db_hdr.dh_sender_length) {
	    ah->name_length = db_hdr.dh_sender_length;
	    ah->sender = alloc_str((int)db_hdr.dh_sender_length);
	    strcpy(ah->sender, db_data.dh_sender);
	} else
	    ah->sender = "";

	if (db_hdr.dh_subject_length) {
	    ah->subj_length = db_hdr.dh_subject_length;
	    ah->subject = alloc_str((int)db_hdr.dh_subject_length);
	    strcpy(ah->subject, db_data.dh_subject);
	} else
	    ah->subject = "";

	ah->replies = db_hdr.dh_replies;
	ah->lines = db_hdr.dh_lines;
	ah->t_stamp = db_hdr.dh_date;

	ah->a_group = (flags & ACC_MERGED_MENU) ? current_group : NULL;

	add_article(ah);
	ah = alloc_art();
    }

#ifdef NOV
    if (dbstatus < 0) {		/* Error reading DB? */
        if ((flags & ACC_EXTRA_ARTICLES) == 0)
	    release_memory(&mem_marker);
	return -2;
    }
    /* else EOF reading DB */
#else
    fclose(data);
#endif /* NOV */
    if ((flags & ACC_DONT_SORT_ARTICLES) == 0)
	sort_articles(-1);
    else
	no_sort_articles();

    if (ignore_re && !ignore_fancy_select && !(flags & ACC_ALSO_READ_ARTICLES)) {
	int nexta, roota, killa, newa;
        for (nexta = killa = newa = roota = 0; nexta < n_articles; nexta++) {
            ah = articles[nexta];
            if (ah->flag & A_ROOT_ART) roota = ah->replies & 127 ;
            if (roota && ah->replies && !auto_select_article(ah,1)) killa++;
            else articles[newa++] = ah;
        }
        n_articles -= killa;  
        killed_articles += killa;
    }

    return n_articles > 0 ? 1 : 0;

data_error:
    log_entry('E', "%s: data inconsistency", gh->group_name);
#ifndef NOV
    fclose(data);
#endif /* NOV */
    if ((flags & ACC_EXTRA_ARTICLES) == 0)
	release_memory(&mem_marker);
    return -12;
}

#ifdef ART_GREP

/* GREP ARTICLE -- search the article for the pattern using the specified function
   Return 1 if pattern occurs in the article else 0
   Also return 0 if any malloc or file open or read error occurs
   Global data_header db_hdr is set by caller
todo:
   doesn't work for folders (hangs)
*/

static int
grep_article(gh, ah, pattern, fcn)
  group_header *gh;
  article_header *ah;
  char *pattern;
  int (*fcn)();
{
	char *line, *end;
	FILE *art;
	news_header_buffer buffer1, buffer2;
	static char *buf;
	static int bufsize, count;
	int size;

	count++;
	art = open_news_article(ah, FILL_OFFSETS |
		(body_search_header ? 0 : SKIP_HEADER), buffer1, buffer2);
	if(!art) {
	  /*msg("Cannot open article");*/
	  return 0;
	}
	size = ah->lpos - ftell(art) + 1;
	if(bufsize < size) {
	  if(buf)
	    free(buf);
	  buf = (char *)malloc(size + 10);
	  if(!buf) {
	    msg("Cannot malloc %d bytes", size);
	    bufsize = 0;
	    return 0;
	  }
	  bufsize = size;
	}
	if(fread(buf, size-1, 1, art) != 1) {
	  fclose(art);
	  /*msg("Cannot read article");*/
	  return 0;
	}
	fclose(art);
	/* print status message every so often */
	if(!(count % 27))
	  msg("Searching %d of %d", 
	    ah->a_number - gh->first_db_article, 
	    gh->last_db_article - gh->first_db_article );

	buf[size] = 0; /* make buf a giant string so strchr below terminates */
	line = buf;
	while((end = strchr(line, '\n'))) {
	  *end++ = 0; /* make the line a nul terminated string */
	  if((*fcn)(pattern, line))
	    return 1;
	  line = end;
	}
	return 0;
}
#endif /* ART_GREP */
