/*
 *	(c) Copyright 1990, Kim Fabricius Storm.  All rights reserved.
 *      Copyright (c) 1996-2026 Michael T Pins.  All rights reserved.
 *
 *	Basic article access and management
 */

#include <string.h>
#include <stdlib.h>
#include "config.h"
#include "global.h"
#include "articles.h"
#include "db.h"
#include "kill.h"
#include "match.h"
#include "news.h"
#include "newsrc.h"
#include "regexp.h"
#include "sort.h"
#include "nn_term.h"

/* articles.c */

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

#ifdef ART_GREP
static int      grep_article(group_header * gh, article_header * ah, char *pattern, int (*fcn) ());
#endif				/* ART_GREP */

int             seq_cross_filtering = 1;
int             select_leave_next = 0;	/* ask to select left over art. */
int             ignore_re = 0;
int             body_search_header = 0;
int             cross_post_limit = 0;
article_number 	n_articles;
article_header	**articles;

extern int      ignore_fancy_select;
extern int      killed_articles;

extern attr_type test_article(article_header *);

/*
 * 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(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(void)
{
    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(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(void)
{
    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(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(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(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(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(void)
{
    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(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(register group_header * gh, article_number first_article, article_number 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;
    long            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)) == -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;

		/* FALLTHROUGH */
	    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(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;
    size_t          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 */
