
/***************************************************************************
 *   Copyright (C) 1999 to 2004 by Jonathan Duddington                     *
 *   email: jonsd@users.sourceforge.net                                    *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 3 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write see:                           *
 *               <http://www.gnu.org/licenses/>.                           *
 ***************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include "akbd.h"
#include "werr.h"
#include "menu.h"
#include "dbox.h"
#include "wimp.h"

#include "narc.h"
#include "hdrs.h"


extern int date_sorter(const void *a1, const void *a2);   /* assembler */
extern int int2_sorter(const void *a1, const void *a2);
extern int msgid_sorter(const void *a1, const void *a2);
extern int msgid_cptr_sorter(const void *a1, const void *a2);

extern void article_view3(TEXTR *t, CARD *cptr);
extern void retrieve_news_article(char *message_id, int local, CARD *cptr_ref);
extern int interpret_article_key(char **text, int *startptr, int end, int *arg, int *arg_end);
extern char *decode_date(int date,int timeflag);
extern char *t_subject(TEXTR *t);

extern FOLDREC list_fr[N_LISTS];
extern OPTIONS options;

extern int main_index_sorted1;    /* 1= sorted by message_id */
extern int main_index_sorted2;
extern int main_index_sorted_break;
extern char status_priority[16];
extern int lock_lists;
extern int lock_expiry;


typedef struct {
	int  right;   /* ix+1,  0=end of chain, -1=no parent */
	int  down;    /* ix+1,  0=no dependent */
	CARD *cptr;
} THREAD_REC;

typedef struct {
	int  acc;
	int  ix;
} THREAD_REC2;

typedef struct {
	CARD *cptr;
	int  ref;
} MSGID_SORT;


static unsigned int *thread_out_ptr;
static CARD *thread_cursor_cptr;
static THREAD_REC *thread_list;
static int thread_level_offset=0;
static int thread_acc;
static int thread_acc2;
static int now_date;

static menu menu_refs = 0;
static unsigned int *menu_refs_data = NULL;
static int menu_refs_size = 0;
static TEXTR *t_menu_refs = NULL;


#define N_MSGID_REFS_LIST  200







int msgid_find3(unsigned int *ixlist, int top1, unsigned int msgid, int control, char *subject)
/***************************************************************************/
/* Use linear search on a list which is sorted by msgid
   Return the entry number in the list, or -1 if not found
   Control: 1  only header-only
            2  outgoing message only
*/
{
	int  pivot;
	int  top;
	int  prev = -1;
	int  bottom= 0;
	unsigned int  m;
	int  ix;
	int  i;
	int  score;
	int  score_max;
	CARD *cptr;
	int  scores[16];
	char subject2[12];

	if(msgid == 0)
		return(-1);
	if((top = top1) <= 0)
		return(-1);

	pivot = top / 2;

	while(pivot != prev)
	{
		prev = pivot;

		cptr = (CARD *)((ixlist[pivot] & IXLIST_MASK) << 2);
		if(msgid < (m = cptr->msgid))
		{
			top = pivot;
		}
		else if( msgid > m)
		{
			bottom = pivot;
		}
		else
		{
			/* if there are multiple matches, find the first */
			while((pivot > 0) && (((CARD *)((ixlist[pivot-1] & IXLIST_MASK)<< 2))->msgid == msgid))
			{
				pivot--;
			}

			/* if more than one, choose i/c rather than o/g and match
			   with the subject if possible */

			score_max = -1;

			if(subject != NULL)
			{
				memcpy(subject2,&subject[6],8);
				subject2[8]=0;
			}

			for(ix=0; ix < 16; ix++)
			{
				if((ix+pivot) >= top1)
					break;

				cptr = (CARD *)((ixlist[ix+pivot] & IXLIST_MASK) << 2);
				if(cptr->msgid != msgid)
					break;

				if(control == 1)
				{
					/* we need a header-only article */
					if((cptr->status & STATUS_BIT_HDR_ONLY) &&
							((cptr->status & STATUS_MASK2) != STATUS_DRAFT))
					{
						score = 5;
						if(subject != NULL)
						{
							/* compare the message-id in the comment line */
							if(strcmp(&cptr->data[cptr->d_comment],subject)==0)
								score += 2;
						}
					}
					else
					{
						score = -10;
					}
				}
				else if(control == 2)
				{
					/* we need an Outgoing article */
					if((cptr->date_box >> 26) >= BOX_BIN)
						score = -11;
					else if(cptr->status & STATUS_BIT_OG)
						score = 10;
					else
						score = -10;
				}
				else
				{
					score = 0;
					if((cptr->status & STATUS_BIT_OG)==0)
						score = 4;

					if((cptr->status & STATUS_BIT_HDR_ONLY)==0)
						score += 1;

					if(subject != NULL)
					{
						if(strcmp_lc(&cptr->data[cptr->d_title],subject)==0)
							score += 2;
						else if(strstr(&cptr->data[cptr->d_title],subject2) != NULL)
							score += 1;
					}
				}
				scores[ix] = score;
				if(score > score_max)
					score_max = score;
			}

			if(score_max < 0)
				return(-1);

			if(ix > 0)
			{
				for(i=0; i<ix; i++)
				{
					if(scores[i] >= score_max)
						return(pivot+i);
				}
			}
			return(pivot);
		}

		pivot = bottom + (top - bottom)/2;
	}

	return(-1);
}   /* end of msgid_find3 */




CARD *msgid_lookup(unsigned int msgid, CARD *cptr_ref, int control)
/*****************************************************************/
/* Find the specified message_id anywhere */
/* control: bits 0-7  pass to msgid_find3
            bit 8     don't sort index (already done for this debatch) */
{
	int  ix;
	unsigned int *ixlist;
	int  n_entries;
	char *subject;

	if(msgid == 0)
		return(NULL);

	if(cptr_ref == NULL)
		subject = NULL;
	else
		subject = &cptr_ref->data[cptr_ref->d_title];

	ixlist = list_fr[0].ixlist;
	n_entries = list_fr[0].n_entries;

	if((control & 0x100) == 0)
	{
		/* ensure the main index is sorted by message-id */
		/* if the top section gets too big, resort the whole list */
		if((main_index_sorted1 != 1) ||
				((list_fr[0].n_entries - main_index_sorted_break) > 3000))
		{
			qsort(ixlist,list_fr[0].n_entries,sizeof(unsigned int),msgid_sorter);
			main_index_sorted1 = 1;
			main_index_sorted2 = 1;
			main_index_sorted_break = list_fr[0].n_entries;
		}
		if(main_index_sorted2 != 1)
		{
			if(main_index_sorted_break < n_entries)
			{
				qsort(&ixlist[main_index_sorted_break],n_entries-main_index_sorted_break,sizeof(unsigned int),msgid_sorter);
			}
			main_index_sorted2 = 1;
		}
	}

	/* search extension section first, then main section of the
	   main index */

	ix = msgid_find3(&ixlist[main_index_sorted_break],n_entries-main_index_sorted_break,msgid,control & 0xff,subject);
	if(ix >= 0)
	{
		ix += main_index_sorted_break;
	}
	else
	{
		ix = msgid_find3(ixlist,main_index_sorted_break,msgid,control & 0xff,subject);
	}

	if(ix < 0)
		return(NULL);
	return((CARD *)((list_fr[0].ixlist[ix] & IXLIST_MASK) << 2));
}   /* end of msgid_lookup */



#ifdef deleted

int msgid_sorter_yield(const void *a1, const void *a2)
/****************************************************/
/* Sort the index by msgid by allow multi-processing */
{
	static int count=0;

	if((count++ & 0xff)==0)
	{
		call_event_process(0);
	}

	return(msgid_sorter(a1,a2));
}   /* end of msgid_sorter_yield */



void index_sort_msgids(int time, void *handle)
/********************************************/
/* Called shortly after Pluto is started to sort the main index
   by message_id */
{
	if((lock_lists == 0) && (lock_expiry == 0))
	{
		set_lock_lists(4,&list_fr[0]);

		if(main_index_sorted1 != 1)
		{
			qsort(list_fr[0].ixlist,list_fr[0].n_entries,sizeof(unsigned int),msgid_sorter_yield);
			main_index_sorted1 = 1;
			main_index_sorted2 = 1;
			main_index_sorted_break = list_fr[0].n_entries;
		}

		clear_lock_lists(4,&list_fr[0]);
	}
	else
	{
		/* busy now, try again later*/
		alarm_set(alarm_timenow() + 20*100,index_sort_msgids, NULL); /* 20 secs */
	}

}   /* end of index_sort_msgids */

#endif


void sort_msgids(void)
/******************/
{
	qsort(list_fr[0].ixlist,list_fr[0].n_entries,sizeof(unsigned int),msgid_sorter);
	main_index_sorted1 = 1;
	main_index_sorted2 = 1;
	main_index_sorted_break = list_fr[0].n_entries;
}   /* end of sort_msgids */





#define N_MSGID_MATCHES  32
int msgid_matches[N_MSGID_MATCHES];
int  n_msgid_matches = 0;


int msgid_find1(MSGID_SORT *ixlist, int top, unsigned int msgid, char *subject)
/**************************************************************************/
/* Use binary search on a list which is sorted by msgid
   Return the entry number in the list, or -1 if not found */
{
	int  pivot;
	int  bottom=0;
	unsigned int  m;
	int  prev = -1;
	CARD *cptr;

	n_msgid_matches = 0;

	if(msgid == 0)
		return(-1);

	pivot = top / 2;

	while(pivot != prev)
	{
		prev = pivot;

		cptr = ixlist[pivot].cptr;
		if(msgid < (m = cptr->msgid))
		{
			top = pivot;
		}
		else if( msgid > m)
		{
			bottom = pivot;
		}
		else
		{
			/* if there are multiple matches, find those with the same subject */
			while((pivot > 0) && (ixlist[pivot-1].cptr->msgid == msgid))
			{
				pivot--;
			}
			bottom = pivot;

			while((pivot < top) && ((cptr = ixlist[pivot].cptr)->msgid == msgid))
			{
				if(strcmp_lc(&cptr->data[cptr->d_title],subject)==0)
				{
					msgid_matches[n_msgid_matches++] = pivot;
					if(n_msgid_matches >= N_MSGID_MATCHES)
						break;
				}
				pivot++;
			}
			if(n_msgid_matches > 0)
				return(n_msgid_matches);

			/* no matches with same subject, so find any */
			pivot = bottom;
			while((pivot < top) && ((cptr = ixlist[pivot].cptr)->msgid == msgid))
			{
				msgid_matches[n_msgid_matches++] = pivot;
				if(n_msgid_matches >= N_MSGID_MATCHES)
					break;
				pivot++;
			}
			return(n_msgid_matches);
		}

		pivot = bottom + (top - bottom)/2;
	}
	return(-1);
}   /* end of msgid_find1 */




void msgid_ref_menu_free()
/************************/
{
	int  i;

	for(i=0; i<menu_refs_size; i++)
	{
		if((menu_refs_data[i] & 0xc0000000)==0)
		{
			if(menu_refs_data[i] != 0)
				free((char *)(menu_refs_data[i]));
		}
	}
	menu_dispose(&menu_refs,0);
	free(menu_refs_data);
	menu_refs_data = NULL;
}   /* end of msgid_ref_menu_free */




void msgid_ref_selected(int *hits)
/********************************/
{
	unsigned int  i;
	CARD *cptr;
	char *msgid;

	i = menu_refs_data[hits[0]];
	if((i & 0xf0000000) == 0xf0000000)
	{
		/* comment line, no action */
	}
	else if(i & 0xc0000000)
	{
		cptr = (CARD *)((i & ~0xf0000000) << 2);
		article_view3(t_menu_refs,cptr);
	}
	else
	{
		msgid = (char *)i;
		retrieve_news_article(msgid,1,t_menu_refs->cptr);
	}

	/* free of any malloced data */
	if(!dbox_persist())
	{
		msgid_ref_menu_free();
	}
}   /* end of msgid_ref_selected */




int msgid_add_reply(unsigned int *refs, int *n_refs, CARD *cptr, int marker)
/**************************************************************************/
{
	int  box;

	box = cptr->date_box >> 26;

	if(box == BOX_DELETED)
		return(0);
	if((cptr->status & STATUS_MASK2) == STATUS_HIDDEN)
		return(0);

	if(box == BOX_BIN)
	{
		if(((cptr->status & STATUS_MASK2) == STATUS_DRAFT) ||
				(cptr->status & STATUS_BIT_OG))
			return(0);
	}

	if(*n_refs < N_MSGID_REFS_LIST)
	{
		refs[(*n_refs)++] = ((int)(cptr) >> 2) + (marker << 28);
		return(1);
	}
	return(0);
}   /* end of msgid_add_reply */




void msgid_list_parents(TEXTR *t)
/*******************************/
{
	int  ix;
	CARD *cptr;
	unsigned int *ixlist;
	int n_entries;
	unsigned int  our_msgid;
	unsigned int  parent_msgid;
	int  key;
	int  start = 0;
	int  refs_start = -1;
	int  refs_end;
	int  i;
	int  j;
	int  argument2;
	int  arg_end;
	CARD *last_cptr;
	int  n_refs=0;
	int  n_refs1;
	int  n_siblings = 0;
	int  n_replies = 0;
	char *p;
	char *ref_string;
	char *tick;
	char *outgoing;
	char *author;
	int  user;
	CARD *cptr_subject;
	wimp_menustr *wmenu_refs;
	unsigned int refs[N_MSGID_REFS_LIST+1];
	char buf[256];
	char author_buf[80];

	visdelay2_begin();

	if(menu_refs_data != NULL)
		msgid_ref_menu_free();

	cptr = t->cptr;
	cptr_subject = t->cptr;
	our_msgid = cptr->msgid;
	parent_msgid = cptr->parent;
	last_cptr = cptr;

	if(t->cptr->status & INET_HDR_MASK)
	{
		while((key = interpret_article_key(&t->text_base,&start,t->internet_header,&argument2,&arg_end)) >= 0)
		{
			switch(key)
			{
			case 8:   /* References */
				refs_start = argument2;
				refs_end = arg_end;
				break;
			}
		}
		if(refs_start >= 0)
		{
			for(ix=1; n_refs < N_MSGID_REFS_LIST; ix++)
			{
				ref_string = get_reference(&t->text_base,refs_start,refs_end,ix);
				if(ref_string == NULL)
					break;

				i = gethash32(ref_string);

				if((parent_msgid==0) && (ix==1))
					parent_msgid = i;

				cptr = msgid_lookup(i,cptr_subject,0);
				if(cptr == NULL)
				{
					/* article not found, note the message-id */
					p = malloc(strlen(ref_string)+1);
					if(p != NULL)
						strcpy(p,ref_string);
					refs[n_refs++] = (int)p;
				}
				else
				{
					/* set top bit to indicate a cptr rather than a msgid string */
					/* is this cptr already in the list ? */
					i = ((int)(cptr) >> 2) + 0x80000000;
					for(j=0; j<n_refs; j++)
					{
						if(refs[j] == i)
							break;
					}
					if(j == n_refs)
					{
						/* No, add this to the list */
						refs[n_refs++] = i;
					}

					last_cptr = cptr;
				}
			}
		}
	}

	/* now look at parent links from the last cptr that was found */
	for(cptr = last_cptr;;)
	{
		cptr = msgid_lookup(cptr->parent,cptr_subject,0);

		if(cptr == NULL)
			break;

		/* is this cptr already in the list ? */
		i = ((int)(cptr) >> 2) + 0x80000000;
		for(ix=0; ix<n_refs; ix++)
		{
			if(refs[ix] == i)
				break;
		}
		if(ix == n_refs)
		{
			/* No, add this to the list */
			refs[n_refs++] = i;
		}
	}

	n_refs1 = n_refs;

	/* look for any siblings and descendants */

	n_entries = list_fr[0].n_entries;
	ixlist = list_fr[0].ixlist;

	if((parent_msgid != 0) && (our_msgid != 0))
	{
		/* look for siblings and descendants */
		for(ix=0; ix < n_entries; ix++)
		{
			cptr = (CARD *)((ixlist[ix] & IXLIST_MASK) << 2);
			if(cptr->parent == parent_msgid)
			{
				n_siblings += msgid_add_reply(refs,&n_refs,cptr,0xc);
			}
			if(cptr->parent == our_msgid)
			{
				n_replies += msgid_add_reply(refs,&n_refs,cptr,0x8);
			}
		}
	}
	else if(our_msgid != 0)
	{
		/* just look for descendants */
		for(ix=0; ix < n_entries; ix++)
		{
			cptr = (CARD *)((ixlist[ix] & IXLIST_MASK) << 2);
			if(cptr->parent == our_msgid)
			{
				n_replies += msgid_add_reply(refs,&n_refs,cptr,0x8);
			}
		}
	}
	else if(parent_msgid != 0)
	{
		/* just look for siblings */
		for(ix=0; ix < n_entries; ix++)
		{
			cptr = (CARD *)((ixlist[ix] & IXLIST_MASK) << 2);
			if(cptr->parent == parent_msgid)
			{
				n_siblings += msgid_add_reply(refs,&n_refs,cptr,0xc);
			}
		}
	}

	menu_refs_data = malloc(sizeof(int) * (n_refs+2));
	i = 0;
	for(ix=n_refs1-1; ix>=0; ix--)
	{
		menu_refs_data[i++] = refs[ix];
	}

	if((n_replies == 0) && (n_siblings == 1) &&
			((refs[n_refs-1] & ~0xf0000000)==((unsigned int)(t->cptr)) >> 2))
	{
		/* no replies or siblings (i.e. only a reference to
		   the specified article */
	}
	else
	{
		if(n_siblings > 0)
		{
			menu_refs_data[i++] = 0xfffffffe;
			start = i;
			for(ix=n_refs1; ix<n_refs; ix++)
			{
				if((refs[ix] & 0xc0000000) == 0xc0000000)
					menu_refs_data[i++] = refs[ix];
			}
			qsort(&menu_refs_data[start],i-start,sizeof(unsigned int),date_sorter);
		}

		if(n_replies > 0)
		{
			menu_refs_data[i++] = 0xffffffff;
			start = i;
			for(ix=n_refs1; ix<n_refs; ix++)
			{
				if((refs[ix] & 0xc0000000) == 0x80000000)
					menu_refs_data[i++] = refs[ix];
			}
			qsort(&menu_refs_data[start],i-start,sizeof(unsigned int),date_sorter);
		}
	}

	menu_refs_size = i;


	/* make menu */
	menu_refs = menu_new("References","");
	for(ix=0; ix<menu_refs_size; ix++)
	{
		if((i = menu_refs_data[ix]) == 0xffffffff)
		{
			strcpy(buf,"|-- Replies --");
		}
		else if(i == 0xfffffffe)
		{
			strcpy(buf,"|-- Siblings --");
		}
		else if(i & 0xc0000000)
		{
			cptr = (CARD *)((i & ~0xf0000000) << 2);

			outgoing = " ";
			if(cptr == t->cptr)
				tick = "!";
			else
				tick = "";

			strncpy0(author_buf,&cptr->data[cptr->d_author],sizeof(author_buf));
			p = &author_buf[strlen(author_buf)];
			while(--p > &author_buf[2])
			{
				if(*p == '<')
				{
					*p = 0;      /* only take name, not address */
					break;
				}
			}
			author = author_buf;

			if((cptr->date_box >> 26) >= BOX_BIN)
			{
				outgoing = " (";
			}
			if(cptr->status & STATUS_BIT_OG)
			{
				outgoing = " ";
				user = cptr->user & 0x1f;
				if(user > 0)
				{
					sprintf(author_buf,"%s",options.mailbox[user-1].full_name);
				}
			}

			sprintf(buf,"%s%s  %s%s",tick,decode_date(cptr->date_box & DATE_MASK,0),outgoing,author);
			strcpy_printable(buf,buf,strlen(buf)+1);

			/* replace characters that cause problems in menu entries */
			for(j=strlen(buf)-1; j>=0; j--)
			{
				if(strchr(",|",buf[j])!=NULL)
					buf[j]=' ';
			}
		}
		else
		{
			strcpy(buf,(char *)i);   /* msgid string */
		}
		menu_extend(menu_refs,buf);
	}
	if(ix==0)
	{
		menu_extend(menu_refs,"(None)");
		menu_refs_data[0] = 0xffffffff;
	}

	visdelay2_end();

	t_menu_refs = t;
	wmenu_refs = (wimp_menustr *)menu_syshandle(menu_refs);
	dbox_menu(wmenu_refs,msgid_ref_selected,NULL);
}   /* end of msgid_list_parents */





static void thread_output(FOLDREC *fr, int ix, int level)
/*******************************************************/
/* Write out this item and its dependents */
{
	int  down;
	int  lev;
	CARD *cptr;
	int  selected;

	lev = level;
	if(thread_level_offset)
	{
		if(lev > fr->display_levels)
		{
			lev -= thread_level_offset;
			if(lev <= fr->display_levels)
				lev = fr->display_levels+1;
		}
	}

	if(lev >= IXLIST_N_LEVELS)
		lev = IXLIST_N_LEVELS-1;

	if((cptr = thread_list[ix].cptr) == thread_cursor_cptr)
		selected = (N_FOLD_LEVELS-1) << IXSEL;  /* lowest level */
	else
		selected = 0;

	*thread_out_ptr++ = (((int)cptr)>>2) + (lev << IXLEV) + selected;
	thread_list[ix].cptr = NULL;

	if(level < fr->display_levels)
		level = fr->display_levels;

	if((down = (thread_list[ix].down-1)) >= 0)
	{
		thread_output(fr, down, level+1);
	}

	if((ix = (thread_list[ix].right-1)) >= 0)
	{
		thread_output(fr, ix, level);
	}
}   /* end of thread_output */




void thread_score(FOLDREC *fr, int ix, int type)
/**********************************************/
{
	CARD *cptr;
	int  i;
	int  down;

	cptr = thread_list[ix].cptr;

	switch(type)
	{
	case 2:    /* status and score */
		i = (0xff - status_priority[cptr->status & STATUS_MASK2]) << 16;
		if(i > thread_acc)
			thread_acc = i;
		if(cptr->score > thread_acc2)
			thread_acc2 = cptr->score;
		break;

	case 3:    /* first date */
		if((cptr->date_box & DATE_MASK) < thread_acc)
			thread_acc = cptr->date_box & DATE_MASK;
		return;   /* don't look at the rest of the thread */

	case 4:    /* latest date */
		if(((cptr->date_box & DATE_MASK) > thread_acc) &&
				((cptr->date_box & DATE_MASK) < now_date))
			thread_acc = cptr->date_box & DATE_MASK;
		break;

	case 5:    /* highest score */
		if(cptr->score > thread_acc)
			thread_acc = cptr->score;
		break;

	}

	if((down = (thread_list[ix].down-1)) >= 0)
	{
		thread_score(fr, down, type);
	}

	if((ix = (thread_list[ix].right-1)) >= 0)
	{
		thread_score(fr, ix, type);
	}
}   /* end of thread_score */





void thread_sort(FOLDREC *fr, int start, int offset)
/*********************************************/
/* Sort a portion of a fr list by thread
   Offset: reduce indentation by this number of levels */
{
	int  ix;
	CARD *cptr;
	MSGID_SORT *ixlist2;
	int  i;
	int  n_refs;
	unsigned int *p_refs;
	int  parent;
	int  n_entries;
	int  level = 0;
	int  initial_level;
	int  n_threads;
	int  thread_count;
	int  thread_sorting;
	int  subject_start;
	char *subject;
	char *prev_subject;
	char *this_subject;
	int  this_date;
	int  prev_date;
	int  diff;
	THREAD_REC2 *thread_list2;
	char buf[40];
	static int thread_acc_init[] = {0,0,0,0x7fffffff,0,0,0,0};
	static int thread_acc2_init[] = {0,0,-0x7fffffff,0,0,0,0,0};

	const int DATE_DIFF = 0x8880;  /* 2 months */

	visdelay2_begin();

	thread_level_offset = offset;
	fr->thread_indent = offset;
	thread_cursor_cptr = (CARD *)((fr->ixlist[fr->cursor_ref] & IXLIST_MASK) << 2);

	/* the list is already sorted by source,subject,date */
	initial_level = fr->ixlist[start] >> IXLEV;

	/* find the end of this level */
	for(ix=start+1; ix<fr->n_entries; ix++)
	{
		if((fr->ixlist[ix] >> IXLEV) <= initial_level)
			break;
	}
	n_entries = ix - start;

	/* sort a copy by message_id */
	ixlist2 = malloc(n_entries * sizeof(MSGID_SORT));
	if(ixlist2 == NULL)
	{
		malloc_err(12);
		return;
	}
	for(ix=0; ix<n_entries; ix++)
	{
		ixlist2[ix].cptr = (CARD *)((fr->ixlist[start+ix] & IXLIST_MASK) << 2);
		ixlist2[ix].ref = ix;
	}
	qsort(ixlist2,n_entries,sizeof(MSGID_SORT),msgid_cptr_sorter);


	thread_list = calloc(sizeof(THREAD_REC),n_entries);
	if(thread_list == NULL)
	{
		malloc_err(13);
		free(ixlist2);
		return;
	}

	/* find the parents (if present) of each of the entires */

	n_threads = 0;
	for(ix=n_entries-1; ix>=0; ix--)
	{
		cptr = (CARD *)((fr->ixlist[start+ix] & IXLIST_MASK) << 2);
		subject = &cptr->data[cptr->d_title];
		thread_list[ix].cptr = cptr;

		if(cptr->parent == 0)
		{
			thread_list[ix].right = -1;   /* no parent reference */
			n_threads++;
			continue;
		}

		parent = -1;
		if(msgid_find1(ixlist2,n_entries,cptr->parent,subject) > 0)
		{
			parent = msgid_matches[0];

			if((n_msgid_matches > 1) && ((cptr->n_refs & N_REFS_MASK) > 0))
			{
				/* look whether any of the matches has our grandparent as its parent */
				p_refs = (unsigned int *)(cptr->data);
				for(i=0; i<n_msgid_matches; i++)
				{
					if(p_refs[0] == ixlist2[msgid_matches[i]].cptr->parent)
					{
						parent = msgid_matches[i];
						break;
					}
				}
			}
		}
		else
		{
			/* parent reference not found - look for more distant ancestors */

			p_refs = (unsigned int *)(cptr->data);
			n_refs = cptr->n_refs & N_REFS_MASK;
			for(i=0; i<n_refs; i++)
			{
				if(msgid_find1(ixlist2,n_entries,p_refs[i],subject) > 0)
				{
					parent = msgid_matches[0];
					break;
				}
			}
		}


		if(parent == -1)
		{
			thread_list[ix].right = -1;   /* no parent */
			n_threads++;
		}
		else
		{
			parent = ixlist2[parent].ref;

			thread_list[ix].right = thread_list[parent].down;
			thread_list[parent].down = ix+1;
		}
	}


	/* now sort the threads by score, or date of latest entry or
	   whatever */
	thread_sorting = fr->display->threading;

	thread_list2 = calloc(n_threads,sizeof(THREAD_REC2));
	thread_count = 0;
	prev_subject = "\r\r\r";
	now_date = get_today_date2(buf) + 576;  /* now plus 24 hours */

	for(ix=0; ix<n_entries; ix++)
	{
		if(thread_list[ix].right == -1)
		{
			/* this item has no parents */
			if(thread_sorting > 1)
			{
				cptr = thread_list[ix].cptr;
				this_subject = &cptr->data[cptr->d_title];
				this_date = cptr->date_box & DATE_MASK;
				if((strcmp_lc(prev_subject,this_subject) != 0) ||
						(date_subtract(this_date,prev_date) > 0xccc0))
				{
					thread_acc = thread_acc_init[thread_sorting];
					thread_acc2 = thread_acc2_init[thread_sorting];
					subject_start = thread_count;
				}

				thread_score(fr,ix,thread_sorting);

				for(i=subject_start; i<=thread_count; i++)
					thread_list2[i].acc = thread_acc + thread_acc2;

				prev_subject = this_subject;
				prev_date = this_date;
			}

			thread_list2[thread_count].ix = ix;
			thread_count++;
		}
	}

	/* sort the threads by their acc value */
	if(thread_sorting > 1)
	{
		qsort(thread_list2,n_threads,sizeof(THREAD_REC2),int2_sorter);
	}

	/* fill in the fr list in tree order */
	thread_out_ptr = &fr->ixlist[start];
	prev_subject = "\r\r\r";

	for(i=0; i<n_threads; i++)
	{
		ix = thread_list2[i].ix;

		cptr = thread_list[ix].cptr;
		this_subject = &cptr->data[cptr->d_title];
		this_date = cptr->date_box & DATE_MASK;
		if((strcmp_lc(prev_subject,this_subject) == 0) &&
				((diff=date_subtract(this_date,prev_date)) < DATE_DIFF) &&
				(-diff < DATE_DIFF))
		{
			thread_output(fr,ix,level+2);
		}
		else
			thread_output(fr,ix,level+1);
		prev_subject = this_subject;
		prev_date = this_date;
	}
	free(thread_list2);


#ifdef deleted
	/* fill in the fr list in tree order */
	thread_out_ptr = &fr->ixlist[start];

	for(ix=0; ix<n_entries; ix++)
	{
		if(thread_list[ix].right == -1)
		{
			/* this item has no parents */
			cptr = thread_list[ix].cptr;
			this_subject = &cptr->data[cptr->d_title];
			if(strcmp_lc(prev_subject,this_subject) == 0)
				thread_output(fr,ix,level+2);
			else
				thread_output(fr,ix,level+1);
			prev_subject = this_subject;
		}
	}
#endif

	free(thread_list);
	free(ixlist2);


	fr->ixlist[start] = (fr->ixlist[start] & ~IXLIST_LEVEL) + (initial_level << IXLEV);
	fr->threading_done = 1;

	visdelay2_end();
}   /* end of thread_sort */



void thread_change_offset(FOLDREC *fr, int shift)
/***********************************************/
/* Change thread indentation */
{
	int  start;
	int  ix;
	int  offset;
	int  max=0;
	int  lev;

	if((fr==NULL) || (fr->ixlist == NULL))
		return;

	if(fr->open_levels < fr->display_levels)
		return;

	start = fr->open_ref[fr->open_levels-1];
	for(ix=0; ix<fr->n_open[fr->open_levels]; ix++)
	{
		if((lev = (fr->ixlist[ix+start] >> IXLEV)) > max)
			max = lev;
	}
	max -= (fr->display_levels+1);

	if(shift)
		offset = -5;
	else
		offset = 5;

	if(offset > max)
		offset = max;

	offset += fr->thread_indent;
	if(offset < 0)
		offset = 0;
	thread_sort(fr,start,offset);

	start = fold_vector_to_line(fr,fr->open_vect,fr->open_levels-1);
	redraw_list_lines(fr,start);
}   /* end of thread_change_offset */





/******************************************************************/
/*               HEADER ONLY NEWS FETCHING                        */
/******************************************************************/


int fetch_list_used = 1;    /* assume there may be a fetch list */
int fetch_list_count = 0;
int fetch_sort_list = 0;
extern int fetch_count;


void load_fetch_list(void)
/**********************/
{
	fetch_list_count = 1;
	fetch_sort_list = 1;
}   /* end of load_fetch_list */



int check_fetch_list(char *message_id)
/********************************/
/* Does this match a stored header only article ? */
{
	int  ix;
	CARD *cptr;
	unsigned int *ixlist;
	int  box;
	int  msgid;
	int  fetching;
	static int ixlist_top=0;

	if(fetch_sort_list == 2)
		return(-1);   /* not expecting any fetched bodies */

	if(fetch_sort_list == 1)
	{
		/* are there any articles still waiting to be fetched ? */
		fetching = posted_read_titles(8,0,NULL) + posted_read_titles(9,0,NULL);

		if(fetching == 0)
		{
			if(fetch_count > 0)
				fetch_count--;
			save_box_any_read();
		}
		else
		{
			fetch_count = 3;
			save_box_any_read();
		}

		if(fetch_count == 0)
		{
			fetch_sort_list = 2;
			return(-1);    /* not expecting any fetched bodies */
		}

		/* ensure that the main index is sorted by message-id, before we
		   start the debatch */
		msgid_lookup(1,NULL,0);
		fetch_sort_list = 0;
		ixlist_top = list_fr[0].n_entries;
	}

	ixlist = list_fr[0].ixlist;

	msgid = gethash32(message_id);

	ix = msgid_find3(&ixlist[main_index_sorted_break],ixlist_top-main_index_sorted_break,msgid,1,message_id);
	if(ix >= 0)
	{
		ix += main_index_sorted_break;
	}
	else
	{
		ix = msgid_find3(ixlist,main_index_sorted_break,msgid,1,message_id);
	}

	if(ix >= 0)
	{
		cptr = (CARD *)((list_fr[0].ixlist[ix] & IXLIST_MASK) << 2);

		if((cptr->status & STATUS_BIT_HDR_ONLY) &&
				(strcmp(&cptr->data[cptr->d_comment],message_id)==0))
		{
			/* delete ths header-only article */
			box = cptr->date_box >> 26;

			if(box < BOX_BIN)
			{
				cptr->status = cptr->status & ~STATUS_MASK2 | STATUS_HIDDEN;
				article_file_ensure_closed((cptr->addr >> 24) & 0xff);
				article_delete(NULL,cptr,1,6);    /* and box->BIN */
			}

			return(box);
		}
		else
		{
			return(-1);
		}
	}
	return(-1);
}   /* end of check_fetch_list */



void free_fetch_list(int news_count,char *time_stamp)
/**********************************************/
{
}   /* end of free_fetch_list */

