
/***************************************************************************
 *   Copyright (C) 1997 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 <ctype.h>
#include <string.h>

#include "menu.h"
#include "event.h"
#include "template.h"
#include "win.h"
#include "wimp.h"
#include "dbox.h"
#include "werr.h"
#include "bbc.h"
#include "xferrecv.h"

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


extern char *expand_source(int source, int explan);


extern FILE *fopen_werr(char *path, char *access, char *name);
extern int fold_next(FOLDREC *fr, int ix, int *level);
extern int fold_line_to_ref(FOLDREC *fr, int line, int *level, int *vector);
extern void fold_open_at_vector(FOLDREC *fr, int *vector, int level);
extern void fold_open_at_line(FOLDREC *fr, int line);
extern void fold_close_level(FOLDREC *fr, int level);
extern int fold_n_lines(FOLDREC *fr, int level);
extern int fold_is_line_open(FOLDREC *fr, int line);

extern char *newsg_lookup_source(int source);

extern char *path_choices;
extern OPTIONS options;
extern FOLDREC list_fr[N_LISTS];
extern int lists_line_height;
extern int lists_font_offset;
extern int scale_x;
extern int standard_font;
extern char temp_fname[];
extern dbox dbox_subset;

void redraw_cats_lines(FOLDREC *fr, int linenum);


typedef struct {
	short catnum;     /* index into array of CATs. Number of this CAT or zero if unused */
	short right;      /* index into array of CATs, next at this level */
	short down;       /* index into array of CATs, first at next level down */
	short position;   /* position in the hierarchy */
	short count;      /* number of times used in article headers */
	short data;
}  CAT_FL;    /* just the fixed length bits */





#define N_SPARE_CATS   50
#define N_SPARE_CAT_CHARS  2000



#define CATS   1
#define SRCS   2
FOLDREC cty_fr;
FOLDREC src_fr;

FOLDREC *global_fr;
static char *cat_fname[] = {"","categories","sources"};


char *menustr_cats = "Add,Add child,Remove,Edit,Sort,Find,List";


/* category window data */


int  category_position=0;  /* used as serial number for position in hierarchy */
static int sort_parent = 0;

static menu menu_category=NULL;
static FOLDREC *fr_menu_category;




extern void (*menu_expected)(int *);
extern int dbox_menu_field;





/******************************************************************************************/
/*  CATEGORY FILE  */
/******************************************************************************************/





void freadstr(char *p, FILE *f)
/*****************************/
/* Read string up to, and including zero byte */
{
	int  c;

	do {
		c = fgetc(f);
		*p++ = c;
	}  while(c != 0);
}   /* end of freadstr */








void load_cats_file(FOLDREC *fr)
/******************************/
/* Read category file and store in memory as linked data */
{
	int  count;
	CAT *cptr;
	CAT  catbuf;
	int  strptr;
	FILE *f;
	CAT_HEADER header;
	char strbuf_name[256];
	char strbuf_comment[256];
	char fname[256];

	sprintf(fname,"%s.%s",path_choices,cat_fname[fr->type]);
	f = fopen(fname,"r");


	if(f == NULL)
	{
		header.n_cat_chars = 1;
		header.n_cats = 0;
		header.max_cat = 0;
		header.cat_start = 0;
	}
	else
	{
		fread(&header,1,sizeof(header),f);
	}

	fr->max_cats = header.max_cat + N_SPARE_CATS;
	fr->cat_data = (CAT *)calloc(fr->max_cats,sizeof(CAT));

	fr->max_cat_chars = header.n_cat_chars + N_SPARE_CAT_CHARS;
	fr->cat_chars = (char *)malloc(fr->max_cat_chars);

	strptr = 1;    /* leave 1st byte = zero in cat_chars */
	fr->cat_chars[0] = 0;

	for(count=0; count < header.n_cats; count++)
	{
		fread(&catbuf,1,sizeof(CAT_FL),f);
		if(feof(f)) break;

		freadstr(strbuf_name,f);
		freadstr(strbuf_comment,f);

		/* copy category to it's position in the array */
		cptr = &fr->cat_data[catbuf.catnum];
		memcpy(cptr,&catbuf,sizeof(CAT_FL));

		/* copy strings */
		cptr->name = strptr;
		strcpy(&fr->cat_chars[strptr],strbuf_name);
		strptr += (strlen(strbuf_name) + 1);

		cptr->comment = strptr;
		strcpy(&fr->cat_chars[strptr],strbuf_comment);
		strptr += (strlen(strbuf_comment) + 1);
	}

	fr->cat_data[0].right = header.cat_start;
	fr->cat_data[0].down = header.cat_start;

	fr->n_open[0] = fold2_count_level(fr,0,0);
	fr->open_levels = 0;
	fr->cursor_ref = 0;

	if(f != NULL)
		fclose(f);

	memcpy(&fr->cat_header,&header,sizeof(header));
}   /* end of load_catfile */







void save_catfile(FOLDREC *fr, int reload)
/****************************************/
/* Save the category data in to file */
{
	char *p1;
	char *p2;
	CAT *cptr;
	int  catix;
	FILE *f;
	CAT_HEADER header;
	char fname[256];

	sprintf(fname,"%s.%s",path_choices,cat_fname[fr->type]);
	f = fopen_werr(fname,"w","Categories ");
	if(f == NULL)
	{
		return;
	}

	/* these are incremented etc. by save_cat_level */
	header.n_cats = 0;
	header.max_cat = 0;
	header.n_cat_chars = 1;

	fwrite(&header,1,sizeof(header),f);

	for(catix=1; catix<fr->max_cats; catix++)
	{
		cptr = &fr->cat_data[catix];

		if(cptr->catnum == 0)
			continue;    /* this entry is unused */

		p1 = &fr->cat_chars[cptr->name];
		p2 = &fr->cat_chars[cptr->comment];

		if(cptr->catnum > header.max_cat)
			header.max_cat = cptr->catnum;

		header.n_cats++;
		header.n_cat_chars += strlen(p1) + strlen(p2) + 2;

		/* write out this category */
		fwrite(cptr,1,sizeof(CAT_FL),f);   /* fixed length part */
		fwrite(p1,1,strlen(p1)+1,f);       /* string, name */
		fwrite(p2,1,strlen(p2)+1,f);       /* string, comment */
	}

	rewind(f);

	header.cat_start = fr->cat_data[0].right;
	fwrite(&header,1,sizeof(header),f);

	fclose(f);

	if(reload)
		load_cats_file(fr);
}  /* end of save_catfile */








/******************************************************************************************/
/*    Specific Fold Management Routines        */
/******************************************************************************************/




int fold2_vector_to_ref(FOLDREC *fr, int *vector, int n_levels)
/*************************************************************/
/* Find index of element in linked tree structure */
{
	int  i;
	int  level;
	int  ix;
	int  n;

	ix = fr->cat_data[0].right;    /* from root */

	for(level=0; level<=n_levels; level++)
	{
		if(level > 0)
			ix = fr->cat_data[ix].down;

		n = vector[level];
		for(i=0; i<n; i++)
		{
			ix = fr->cat_data[ix].right;
		}
	}
	return(ix);

}   /* end of fold2_vector_to_ref */






int fold2_count_level(FOLDREC *fr, int ix, int level)
/***************************************************/
/* How many dependents of this level of the vector,
   from position ix in the index list  */
{
	int  i;

	if((ix = fr->cat_data[ix].down) == 0)
		return(0);

	i = 1;
	while((ix = fr->cat_data[ix].right) != 0)
	{
		i++;
	}
	return(i);
}   /* end of fold_count_level */




int fold2_next_right(FOLDREC *fr, int ix, int level)
/**************************************************/
/* Find next item at the same level */
{
	int  i;
	i = fr->cat_data[ix].right;

	if(i == 0)
		return(-1);
	return(i);
}   /* end of fold_next_right */




int fold2_next_down(FOLDREC *fr, int ix, int level)
/*************************************************/
/* Find next item at lower level */
{
	int  i;
	i = fr->cat_data[ix].down;

	if(i == 0)
		return(-1);
	return(i);
}   /* end of fold_next_down */




/******************************************************************************************/
/******************************************************************************************/



void category_set_positions2(FOLDREC *fr,int cat)
/***********************************************/
{
	CAT *cptr;

	do {
		cptr = &fr->cat_data[cat];
		cptr->position = category_position++;
		if(cptr->down != 0)
			category_set_positions2(fr,cptr->down);
	} while((cat = cptr->right) != 0);
}   /* end of category_set_positions2 */



void category_set_positions(FOLDREC *fr)
/**************************************/
/* Climb through the category hierarchy and assign position numbers */
{
	category_position=1;

	category_set_positions2(fr,fr->cat_header.cat_start);
}   /* end of category_set_positions */




int category_find_parent(FOLDREC *fr, int ref)
/********************************************/
/* Find the parent of this category */
{
	int cat;
	int  cat2;

	for(cat=0; cat<fr->cat_header.n_cats; cat++)
	{
		if((cat2 = fr->cat_data[cat].down) != 0)
		{
			do {
				if(cat2 == ref)
					return(cat);

				cat2 = fr->cat_data[cat2].right;
			}   while(cat2 != 0);
		}
	}
	return(-1);
}   /* end of category_find_parent */



void expand_cat_string(short *cats, int n_cats, char *buf, int n_buf)
/*******************************************************************/
{
	int  i;
	char *p;
	char *name;
	int  len;

	p = buf;
	for(i=0; (i < n_cats) && (p-buf < 128-16); i++)
	{
		name = category_name(&cty_fr,cats[i]);
		len = strlen(name);
		if((p+len) >= (buf+n_buf))
			break;

		sprintf(p,"%s ",name);
		p+=(len+1);
	}
	*p = 0;

}   /* end of expand_cat_string */






void set_cats_extent(FOLDREC *fr,int open)
/****************************************/
/* open: bit 0  open window
         bit 1  bring to front */
{
	int size;
	wimp_wstate wstate;

	size = fold_n_lines(fr,0) * lists_line_height;

	fr->workarea.box.y0 = fr->workarea.box.y1 - size;
	fr->workarea.w = fr->window;
	wimp_set_extent(&fr->workarea);

	if(open)
	{
		wimp_get_wind_state(fr->window,&wstate);
		wstate.o.box.y0 = wstate.o.box.y1 - size;

		if(open & 2)
			wstate.o.behind = -1;   /* at front */

		wimp_open_wind(&wstate.o);
	}
}   /* end of set_cats_extent */



void open_cats_window()
/*********************/
{
	set_cats_extent(&cty_fr,3);
}   /* end of open_cats_window */


void open_srcs_window()
/*********************/
{
	set_cats_extent(&src_fr,3);
}   /* end of open_srcs_window */




int expand_cats_table(FOLDREC *fr, int increase)
/**********************************************/
{
	int  old_max;
	CAT *p;

	old_max = fr->max_cats;
	fr->max_cats += increase;
	p = (CAT *)calloc(fr->max_cats,sizeof(CAT));

	if(p == NULL)
		return(-1);

	memcpy(p,fr->cat_data,old_max*sizeof(CAT));
	free(fr->cat_data);
	fr->cat_data = p;
	return(0);
}   /* end of expand_cats_table */



int expand_cats_chars(FOLDREC *fr)
/********************************/
{
	int  i;
	char *p;

	i = fr->max_cat_chars + N_SPARE_CAT_CHARS;
	p = (char *)malloc(i);

	if(p == NULL)
		return(-1);

	memcpy(p,fr->cat_chars,i);
	free(fr->cat_chars);
	fr->cat_chars = p;
	fr->max_cat_chars = i;
	return(0);
}   /* end of expand_cats_chars */




void category_add_chars(FOLDREC *fr, char *name, unsigned short *index)
/*********************************************************************/
{
	int  n;

	n = strlen(name);
	*index = fr->cat_header.n_cat_chars;
	fr->cat_header.n_cat_chars += (n+1);
	if(fr->cat_header.n_cat_chars >= fr->max_cat_chars)
	{
		if(expand_cats_chars(fr) < 0)
			return;
	}
	strcpy(&fr->cat_chars[*index],name);
}   /* end of category_add_chars */







int category_count_articles(int type ,int cat, int new_cat)
/*********************************************************/
/* Count occurances of a source or a category in the card file.
   If new_cat > 0 , this replaces the occurances */
{
	int  n;
	int  count;
	CARD *cptr;
	short *s;
	int  ix;
	unsigned int *ixlist;
	int n_entries;

	if((new_cat > 0) && (check_lock_lists(0xffff) != 0))
		return(0);

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

	count = 0;
	for(ix=0; ix<n_entries; ix++)
	{
		cptr = (CARD *)((ixlist[ix] & IXLIST_MASK) << 2);
		if(((cptr->date_box >> 26) >= BOX_BIN) || ((cptr->status & STATUS_MASK2)==STATUS_HIDDEN))
			continue;    /* ignore any in the Bin box */

		if(type == 1)
		{
			/* count occurances of category */
			s = (short *)(cptr->data + (cptr->n_refs & N_REFS_MASK)*sizeof(int));
			for(n=0; n<(cptr->n_cats & N_CATS_MASK); n++)
			{
				if(s[n] == cat)
				{
					if(new_cat > 0)
					{
						/* replace by new category number */
						s[n] = new_cat;

						/* check for duplicate category */
						/* ???? */


						article_update(NULL,cptr,NULL,0,0);
					}
					count++;
					break;
				}
			}
		}
		else if(type == 2)
		{
			/* count occurances of source */
			if(cptr->source == cat)
			{
				if(new_cat > 0)
				{
					cptr->source = new_cat;
					article_update(NULL,cptr,NULL,0,0);
				}
				count++;
			}
		}
	}
	return(count);
}   /* end of category_count_articles */







int category_change_name2(FOLDREC *fr, CAT *cptr, char *name, char *comment)
/**************************************************************************/
{
	int  n_length = 0;
	int  c_length = 0;

	if((cptr != NULL) && (cptr->catnum > 0))
	{
		if(cptr->name != 0)
			n_length = strlen(&fr->cat_chars[cptr->name]);

		if(cptr->comment != 0)
			c_length = strlen(&fr->cat_chars[cptr->comment]);
	}


	if(strlen(name) > n_length)
	{
		cptr->name = fr->cat_header.n_cat_chars;
		fr->cat_header.n_cat_chars += (strlen(name) + 1);
		if(fr->cat_header.n_cat_chars >= fr->max_cat_chars)
		{
			if(expand_cats_chars(fr) < 0)
				return(-1);
		}
	}
	strcpy(&fr->cat_chars[cptr->name],name);

	if(strlen(comment) > c_length)
	{
		cptr->comment = fr->cat_header.n_cat_chars;
		fr->cat_header.n_cat_chars += (strlen(comment) + 1);
		if(fr->cat_header.n_cat_chars >= fr->max_cat_chars)
		{
			if(expand_cats_chars(fr) < 0)
				return(-1);
		}
	}
	strcpy(&fr->cat_chars[cptr->comment],comment);

	return(0);
}   /* end of category_change_name2 */



void category_change_name(FOLDREC *fr, int cat, char *name, char *comment)
/*****************************************************************/
/* called from edit newsgroup, change the name of a source */
{
	CAT *cptr;

	cptr = &fr->cat_data[cat];

	if(comment == NULL)
		comment = &fr->cat_chars[cptr->comment];

	category_change_name2(fr,cptr,name,comment);

	redraw_cats_lines(fr,0);
	save_catfile(fr,0);
}   /* end of category_change_name */




int category_edit(FOLDREC *fr, CAT *cptr)
/***************************************/
{
	dbox d;
	int  n_articles;
	int  n_filters;
	int  action;
	int  catnum;

	char name[129];
	char comment[129];

	if(fr->type == 1)
		d = dbox_new("Cat");
	else
		d = dbox_new("Src");

	catnum = cptr->catnum;

	if((cptr != NULL) && (catnum > 0))
	{
		dbox_setfield(d,1,&fr->cat_chars[cptr->name]);
		dbox_setfield(d,2,&fr->cat_chars[cptr->comment]);

		n_articles = category_count_articles(fr->type,catnum,0);
		n_filters = killfile_count_cats(fr->type,catnum,0);

		dbox_setnumeric(d,3,n_articles+n_filters);
	}

	dbox_show(d);
	action = dbox_fillin(d);

	switch(action)
	{
	case 0:   /* Update */
		dbox_getfield(d,1,name,128);
		dbox_getfield(d,2,comment,128);
		dbox_dispose(&d);

		return(category_change_name2(fr,cptr,name,comment));
		break;

	case 6:   /* Unlink from hierarchy */
	case 7:   /* Delete category completely */
	default:
		dbox_dispose(&d);
		return(-1);
		break;

	}
}   /* end of category_edit */




void category_link(FOLDREC *fr, int ref, int child,int link_from, int level)
/**************************************************************************/
{
	CAT *cptr;
	CAT *cptr_prev;

	cptr = &fr->cat_data[ref];
	cptr_prev = &fr->cat_data[link_from];

	if(child && (link_from != 0))
	{
		cptr->right = cptr_prev->down;
		cptr->parent = link_from;
		cptr_prev->down = ref;

		fold_open_at_vector(fr,fr->cursor_vect,fr->cursor_level);
		fr->cursor_ref = cptr->catnum;
		fr->cursor_level = level+1;
	}
	else
	{
		cptr->right = cptr_prev->right;
		cptr->parent = cptr_prev->parent;
		cptr_prev->right = ref;

		if(link_from == 0)
			cptr_prev->down = ref;

		fr->n_open[level] += 1;
		fr->cursor_ref = cptr->catnum;
		fr->cursor_level = level;
	}

	fr->cat_header.n_cats++;

}   /* end of category_link */




int category_alloc(FOLDREC *fr, int new_ref)
/******************************************/
/* Allocate a new category entry */
{
	int  i;
	CAT *cptr;

	if(new_ref >= fr->max_cats)
	{
		if(expand_cats_table(fr, new_ref - fr->max_cats + 3) < 0)
			new_ref = 0;
	}

	if((new_ref > 0) && (fr->cat_data[new_ref].catnum == 0))
	{
		/* the preferred new category number has already been specified */
		i = new_ref;
	}
	else
	{
		/* find free slot in category table */
		for(i=1; i<fr->max_cats; i++)
		{
			if(fr->cat_data[i].catnum == 0)
			{
				break;
			}
		}
	}

	if((fr->type == 2) && (i >= 255))
	{
		/* limit on number of sources */
		werr(0,"Limit (256) reached on number of Sources");
		return(0);
	}

	if(i==fr->max_cats)
	{
		if(expand_cats_table(fr, N_SPARE_CATS) < 0)
			return(-1);   /* can't expand */

		return(category_alloc(fr,0));   /* try again with larger table */
	}

	cptr = &fr->cat_data[i];
	memset(cptr,0,sizeof(CAT));   /* initialise the new entry */
	cptr->catnum = i;
	return(i);
}   /* end of category_alloc */




void category_new(FOLDREC *fr, int child,int link_from, int level)
/****************************************************************/
{
	int ref;
	CAT *cptr;
	CAT *cat_data;

	cat_data = fr->cat_data;

	ref = category_alloc(fr,0);
	if(ref < 0) return;

	cptr = &cat_data[ref];

	if(category_edit(fr,cptr) < 0)
	{
		cptr->catnum = 0;   /* de-allocate this entry again */
		return;
	}

	/* set link from previous */
	category_link(fr,ref,child,link_from,level);

	/* now sort the siblings into alphabetic order ? */

	/* find their positions in the hierarchy */
	category_set_positions(fr);

	/* set cat_fr.cursor_vector ??? */

}   /* end of category_new */




void category_unlink(FOLDREC *fr,int ref)
/***************************************/
{
	int  cat;
	CAT *cptr;

	cptr = &fr->cat_data[ref];

	/* look for references from other categories */
	for(cat=0; cat < fr->max_cats; cat++)
	{
		if((cat > 0) && (fr->cat_data[cat].catnum == 0))
			continue;    /* not in use */

		if(fr->cat_data[cat].right == ref)
			fr->cat_data[cat].right = cptr->right;

		if(fr->cat_data[cat].down == ref)
			fr->cat_data[cat].down = cptr->right;
	}

	fr->cat_header.n_cats--;
}   /* end of category_unlink */



int category_change_cat(FOLDREC *fr, int cat, int count, int count2)
/******************************************************************/
{
	dbox d;
	int  cat_num = 0;
	char buf[160];

	d = dbox_new("CatChange");

	sprintf(buf,"'%s' is being used by %d articles and %d filters. Change all to:",category_name(fr,cat),count,count2);
	dbox_setfield(d,2,buf);

	dbox_show(d);
	while(cat_num == 0)
	{

		if(dbox_fillin(d) == 0)
		{
			dbox_getfield(d,1,buf,sizeof(buf));
			cat_num = category_lookup(fr,buf,0);
			if(cat_num == 0)
			{
				werr(0,"New name '%s' not recognised",buf);
			}
		}
		else
			break;
	}
	dbox_dispose(&d);
	return(cat_num);
}   /* end of category_change_cat */




void category_remove(FOLDREC *fr, int child,int ref, int level)
/*************************************************************/
{
	CAT *cptr;
	CAT *cat_data;
	int  new_cat;
	int  count;
	int  count2;
	int  ix;
	char *name;
	static char *first_be = " must first be removed from the ";

	cat_data = fr->cat_data;
	cptr = &cat_data[ref];

	if(cptr->down != 0)
	{
		werr(0,"Can't remove category with dependents");
		return;
	}

	if(fr->type == 2)
	{
		/* check whether this Source is used by newsgroups or Users */
		if((name = newsg_lookup_source(ref)) != NULL)
		{
			if(memcmp_lc(name,"mail.",5)==0)
				werr(0,"'%s'%sMailing Lists list",&name[5],first_be);
			else
				werr(0,"'%s'%sNewsgroups List",name,first_be);
			return;
		}

		name = &fr->cat_chars[cptr->name];
		if(memcmp_lc(name,"mail.",5)==0)
		{
			/* is this the name for a User ? */
			for(ix=0; ix<N_MAIL_BOX; ix++)
			{
				if(strcmp_lc(&name[5],options.mailbox[ix].email_addr)==0)
				{
					werr(0,"'%s'%sUsers List",&name[5],first_be);
					return;
				}
			}
		}
	}

	count = category_count_articles(fr->type,ref,0);
	count2 = killfile_count_cats(fr->type,ref,0);

	if((count > 0) || (count2 > 0))
	{
		/* transfer articles to another catagory ? or to parent ? */
		new_cat = 0;

		new_cat = category_change_cat(fr,ref,count,count2);

		if(new_cat > 0)
		{
			category_count_articles(fr->type,ref,new_cat);
			killfile_count_cats(fr->type,ref,new_cat);
		}
		else
			return;
	}

	category_unlink(fr,ref);
	fr->cat_chars[cptr->name] = 0;

	/* ??? check for removing cat with dependents */
	/* ??? check for removing cat in open chain */

	if(--fr->n_open[level] <= 0)
	{
		fr->open_levels--;
	}

	cptr->catnum = 0;  /* remove */

	/* recelculate number open at each level */
	fold_open_at_vector(fr,fr->open_vect,fr->open_levels-1);
}   /* end of category_remove */




int category_sorter(const void *a1, const void *a2)
/*************************************************/
{
	int n1, n2;
	char *p;
	char buf[150];

	if(global_fr->type == 2)
	{
		/* sort on the source name as it appears in article lists,
		   i.e. comment (if there is one) or name (if no comment) */

		strcpy(buf,expand_source(*(int *)a1,2));
		p = expand_source(*(int *)a2,2);
		return(strcmp_lc(buf,p));
	}
	else
	{
		n1 = global_fr->cat_data[*(int *)a1].name;
		n2 = global_fr->cat_data[*(int *)a2].name;

		return(strcmp_lc(&global_fr->cat_chars[n1], &global_fr->cat_chars[n2]));
	}
}   /* end of category_sorter */




void category_sort(FOLDREC *fr, int parent)
/*****************************************/
/* Sort the dependents of this category */
{
	int  i;
	int  ref;
	int  count;
	int *cat_list;

	if(parent >= 0)
	{
		/* count number of dependents */
		ref = fr->cat_data[parent].down;
		if(ref == 0)
			return;   /* no dependents */

		count = 1;
		while((ref = fr->cat_data[ref].right) != 0)
			count++;

		/* make a list of points to the dependents */
		cat_list = (int *)malloc(count * sizeof(int *));
		if(cat_list == NULL)
			return;

		ref = fr->cat_data[parent].down;
		for(i=0; i<count; i++)
		{
			cat_list[i] = ref;
			ref = fr->cat_data[ref].right;
		}

		/* sort the list */
		global_fr = fr;
		qsortG(cat_list,count,sizeof(int *),category_sorter);

		/* re-order the links */
		fr->cat_data[parent].down =  cat_list[0];
		if(parent == 0)
			fr->cat_data[parent].right = cat_list[0];

		for(i=1; i<count; i++)
		{
			fr->cat_data[cat_list[i-1]].right = cat_list[i];
		}
		fr->cat_data[cat_list[i-1]].right = 0;

		free(cat_list);
	}
	category_set_positions(fr);
}   /* end of category_sort */







void deselect_cats(FOLDREC *fr)
/****************************/
/* Deselect all categories */
{
	int  ix;

	for(ix=0; ix<fr->max_cats; ix++)
	{
		fr->cat_data[ix].flags &= ~1;
	}
}   /* end of deselect_cats */




void category_search(FOLDREC *fr, int fields)
/*******************************************/
/* Search for a specified string in a category list.
   fields: 0=name only,  1=name+comment */
{
	dbox d;
	static char string[64] = 0;

	/* get search string */
	d = dbox_new("CatFind");
	dbox_setfield(d,1,string);
	dbox_show(d);
	if(dbox_fillin(d) != 0)
	{
		dbox_dispose(&d);
		return;
	}

	dbox_getfield(d,1,string,sizeof(string)-1);

	/* search from currently selected item, hierarchically, until end */

	/* start at top if non is selected,
	   do children, then right siblings, recursively.
	   When end, look for parent to continue */

	dbox_dispose(&d);
}   /* end of category_search */






int category_add_child(FOLDREC *fr, const char* name, const char *parent_name, int *ref_out, int cat_preferred)
/***************************************************************************************************/
/* Add a name if it doesn't already exist */
{
	int  ref;
	int  parent;
	CAT *cptr;
	CAT *cat_data;

	if((ref = category_lookup(fr,name,0)) > 0)
	{
		return(0);   /* already exists */
	}

	if(parent_name == NULL)
		parent = 0;
	else
	{
		parent = category_lookup(fr,parent_name,0);

		if(parent == 0)
		{
			/* first add the parent */
			category_add_child(fr,parent_name,NULL,&parent,0);
			category_sort(&src_fr,0);
		}

		sort_parent = parent;

	}


	cat_data = fr->cat_data;
	ref = category_alloc(fr,cat_preferred);
	if(ref < 0) return(0);

	cptr = &cat_data[ref];

	cptr->name = fr->cat_header.n_cat_chars;
	fr->cat_header.n_cat_chars += (strlen(name) + 1);
	if(fr->cat_header.n_cat_chars >= fr->max_cat_chars)
	{
		if(expand_cats_chars(fr) < 0)
			return(0);
	}
	strcpy(&fr->cat_chars[cptr->name],name);

	category_link(fr,ref,1,parent,0);

	if(ref_out != NULL)
		*ref_out = ref;
	return(1);
}   /* end of category_add_child */




void category_add_mail_name(char *name, int save)
/***********************************************/
{
	char buf[86];

	sprintf(buf,"mail.%s",name);
	category_add_child(&src_fr,buf,"mail",NULL,0);

	if(save)
	{
		set_cats_extent(&src_fr,0);
		category_sort(&src_fr,sort_parent);
		redraw_cats_lines(&src_fr,0);
		save_catfile(&src_fr,1);
	}
}   /* end of category_add_mail_name */




void redraw_cats_oneline(FOLDREC *fr, int line)
/*********************************************/
{
	wimp_redrawstr r;

	/* give workarea coordinates of line to be redrawn */
	r.w = fr->window;
	r.box.x0 = fr->workarea.box.x0;
	r.box.x1 = fr->workarea.box.x1;
	r.box.y0 = fr->workarea.box.y1 - ((line+1)*lists_line_height);
	r.box.y1 = fr->workarea.box.y1 - (line*lists_line_height);
	wimp_force_redraw(&r);
}   /* end of redraw_category */




void redraw_cats_lines(FOLDREC *fr, int linenum)
/**********************************************/
/* redraw from this line downwards */
{
	wimp_redrawstr r;

	/* give workarea coordinates of line to be redrawn */
	r.w = fr->window;
	r.box.x0 = fr->workarea.box.x0;
	r.box.x1 = fr->workarea.box.x1;
	r.box.y0=  fr->workarea.box.y0;
	r.box.y1=  fr->workarea.box.y1 - (linenum*lists_line_height);
	wimp_force_redraw(&r);
}   /* end of redraw_cats_lines */



void category_display_window(FOLDREC *fr,wimp_redrawstr *r,int start_line,int end_line,int x,int y)
/*************************************************************************************************/
{
	int  line;
	int  level;
	int  max_line;
	int  cat;
	CAT *cptr;
	int  n_chars;
	int  indent;
	int  dependents;
	int  i;
	int  width;
	int  tab_posn;
	os_regset regs;
	char linebuf[128];
	char comment_string[128];


	if(fr->type == 1)
		tab_posn = 250;
	else
	{
		tab_posn = 550;

		if(standard_font==0)
			tab_posn+=50;
	}

	max_line = fold_n_lines(fr,0) - 1;

	cat = fold_line_to_ref(fr, start_line, &level, NULL);

	if(end_line > max_line)
		end_line = max_line;

	for(line=start_line; line<=end_line; line++)
	{
		cptr = &fr->cat_data[cat];

		/* start with indent */
		if(level < 0) level = 0;
		indent = level*32;

		if(cptr->down != 0)
			dependents = '*';
		else
			dependents = ' ';

		sprintf(linebuf,"%c%-7s ",dependents,&fr->cat_chars[cptr->name]);
		strcpy_printable(linebuf,linebuf,sizeof(linebuf));

		strcpy_printable(comment_string ,&fr->cat_chars[cptr->comment],sizeof(comment_string));

		/* write to the screen */
		if(cptr->catnum == fr->cursor_ref)
			list_set_colour(0, 0,x,y);
		else
			list_set_colour(0, 1,x,y);

		if(standard_font >= 0)
		{
			n_chars = strlen(linebuf);

			regs.r[0] = standard_font;
			regs.r[1] = (int)linebuf;
			regs.r[2] = 0x00180;   /* bits 7, 8, */
			regs.r[3] = 0x7fffffff;
			regs.r[4] = 0x7fffffff;
			regs.r[7] = strlen(linebuf);
			os_swi(0x400a1+os_X,&regs);     /* Font_ScanString */

			width = regs.r[3] / scale_x;

			regs.r[1] = (int)linebuf;
			regs.r[2] = 0x190;    /* bit 4, bit 7, bit 8 */
			regs.r[3] = x+indent;
			regs.r[4] = y;
			regs.r[7] = n_chars;
			os_swi(0x40086+os_X,&regs);    /* Font_Paint */

			if(comment_string[0] != 0)
			{
				i = indent+width+20;
				if(i < tab_posn)
					i = tab_posn;

				regs.r[1] = (int)comment_string;
				regs.r[3] = x+i;
				regs.r[7] = strlen(comment_string);
				os_swi(0x40086+os_X,&regs);
			}
		}
		else
		{
			bbc_move(x+indent, y-4);
			printf("%s",linebuf);
			i = strlen(linebuf)*16 + 32;
			if(i < tab_posn)
				i = tab_posn;
			bbc_move(x+i, y-4);
			printf("%s",comment_string);
		}

		y -= lists_line_height;

		cat = fold_next(fr, cat, &level);
		if(cat < 0)
			break;
	}
}   /* end of category_display_window */




void key_category_window(FOLDREC *fr, wimp_eventdata *d)
/******************************************************/
{
	int  c;

	switch(c = d->key.chcode)
	{
	case PAGE_UP:
		scroll_page(d->c.w,1);
		break;

	case PAGE_DOWN:
		scroll_page(d->c.w,-1);
		break;

	case KEY_CT_DOWN:
		scroll_to_top(d->c.w,1);
		break;

	case KEY_CT_UP:
		scroll_to_top(d->c.w,-1);
		break;

	case F_KEY+4:     /* search for string */
		category_search(fr,1);
		break;

	case F_KEY+5:     /* search only for category name */
		category_search(fr,0);
		break;

	default:
		wimp_processkey(c);
		break;
	}

}   /* end of key_category_window */





void click_category_window(FOLDREC *fr,wimp_eventdata *d)
/*******************************************************/
{
	int  i;
	int  x;
	int  y;
	int  catix;
	int  lev;
	int  linenum;

	wimp_wstate wstate;

	x = d->but.m.x;
	y = d->but.m.y;      /* screen coord in click */

	wimp_get_wind_state(fr->window,&wstate);
	i = wstate.o.box.y1 - y;   /* displacement down the window */
	y = i - wstate.o.y;        /* work area coordinate */

	i = x - wstate.o.box.x0;
	x = i + wstate.o.x;


	/* find category from workarea coords */
	linenum = y/lists_line_height;
	catix = fold_line_to_ref(fr,linenum,&lev,fr->cursor_vect);

	switch(d->but.m.bbits)
	{
	case wimp_BCLICKRIGHT:   /* adjust */
	case wimp_BCLICKLEFT:    /* select */
		fr->cursor_ref = catix;
		fr->cursor_level = lev;

		if((fr->cat_data[catix].flags & 1) == 0)
		{
			/* select this item, de-select all others */
			deselect_cats(fr);
			fr->cat_data[catix].flags |= 1;
			redraw_cats_lines(fr,0);    /* redraw all */
		}
		break;


	case wimp_BLEFT:   /* double click */
		if((lev = fold_is_line_open(fr,linenum)) < 0)
		{
			redraw_cats_lines(fr,0);   /* ???? linenum-1 */
			fold_open_at_line(fr,linenum);
			set_cats_extent(fr,1);
			redraw_cats_lines(fr,0);   /* ???? linenum-1 */
		}
		else
		{
			redraw_cats_lines(fr,0);   /* ???? linenum-1 */
			fold_close_level(fr,lev);
			set_cats_extent(fr,1);
		}
		if(fr->cat_data[catix].down != 0)
		{
			break;
		}
		/* else, terminal node, no dependents, drop though to edit */

	case wimp_BRIGHT:
		category_edit(fr,&fr->cat_data[fr->cursor_ref]);
		redraw_cats_lines(fr,0);
		save_catfile(fr,0);
		break;
	}

	set_focus(fr->window);
}   /* end of click_category_window */





void cats_window_handler(wimp_eventstr *e, void *handle)
/******************************************************/
{
	int  more;
	int filetype;
	char *filename;
	int  min_y, max_y;
	int  x,y;
	int  line;
	int  end_line;


	FOLDREC *fr;
	wimp_redrawstr r;

	fr = (FOLDREC *)handle;

	switch (e->e)
	{
	case wimp_EREDRAW:
		r.w = e->data.o.w;
		wimp_redraw_wind(&r, &more);

		while(more)
		{
			min_y = (r.box.y1 - r.g.y1) - r.scy;
			max_y = (r.box.y1 - r.g.y0) - r.scy;

			line = min_y / lists_line_height;
			end_line = (max_y / lists_line_height) + 1;

			x = r.box.x0 - r.scx + 2;
			y = r.box.y1 - r.scy - (line * lists_line_height);
			y -= lists_font_offset;

			category_display_window(fr,&r,line,end_line,x,y);

			wimp_get_rectangle(&r, &more);
		}
		break;

	case wimp_EOPEN:
		wimp_open_wind(&e->data.o);
		break;

	case wimp_ECLOSE:  /* Pass on close request */
		wimp_close_wind(e->data.o.w);
		break;

	case wimp_EBUT:
		click_category_window(fr,&e->data);
		break;

	case wimp_EKEY:
		key_category_window(fr,&e->data);
		break;

	case wimp_ESCROLL:
		scroll_window(&e->data);
		break;


	case wimp_ESEND:
	case wimp_ESENDWANTACK:
		switch(e->data.msg.hdr.action)
		{
		case wimp_MDATALOAD:
			filetype = xferrecv_checkinsert(&filename);
			import_cats_file(filename,filetype);
			xferrecv_insertfileok();
			break;
		}
		break;

	default:   /* Ignore any other event */
		break;
	}
}   /* end of cats_window_handler */




void category_make_list(FOLDREC *fr)
/**********************************/
/* Generate a list of category/source numbers and names */
{
	short *s;
	int  n;
	int  ix;
	CARD *cptr;
	int  max_cat;
	FILE *f;
	int  cat;
	CAT *catptr;
	int  dependents;
	int  *counts;
	int  count2;
	int  tab;
	unsigned int *ixlist;
	char *comment_string;
	char buf[256];

	if(temp_fname[0] != 0)
		remove(temp_fname);

	/* save to wimp$scrap then open file */
	strcpy(temp_fname,get_temp_fname());

	f = fopen_werr(temp_fname,"w",NULL);
	if(f==NULL)
		return;

	visdelay2_begin();
	max_cat = fr->cat_header.max_cat;
	counts = calloc(sizeof(int),max_cat+1);

	ixlist = list_fr[0].ixlist;
	for(ix=0; ix<list_fr[0].n_entries; ix++)
	{
		cptr = (CARD *)((ixlist[ix] & IXLIST_MASK) << 2);
		if((cptr->date_box >> 26) >= BOX_BIN)
			continue;    /* ignore any in the Bin box */

		if(fr->type == 2)
		{
			if(cptr->source <= max_cat)
				counts[cptr->source]++;
		}
		else
		{
			s = (short *)(cptr->data + (cptr->n_refs & N_REFS_MASK)*sizeof(int));
			for(n=0; n<(cptr->n_cats & N_CATS_MASK); n++)
			{
				if(s[n] <= max_cat)
					counts[s[n]]++;
			}
		}
	}

	fprintf(f," Num Articles Filters Name\n\n");
	if(fr->type==1)
		tab = 40;
	else
		tab = 55;

	for(cat=1; cat<=max_cat; cat++)
	{
		catptr = &fr->cat_data[cat];

		if(catptr->down != 0)
			dependents = '*';
		else
			dependents = ' ';

		count2 = killfile_count_cats(fr->type,cat,0);

#ifdef deleted
		/* for testing: include position in list */
		sprintf(buf,"%3d%4d%c%8d%8d  %s",cat,catptr->position,dependents,counts[cat],count2,&fr->cat_chars[catptr->name]);
#endif
		sprintf(buf,"%3d%8d%8d  %s",cat,counts[cat],count2,&fr->cat_chars[catptr->name]);

		comment_string = &fr->cat_chars[catptr->comment];
		if(comment_string[0] != 0)
		{
			n = strlen(buf);
			while(n < tab)
				buf[n++] = ' ';
			sprintf(&buf[n],"   %s",comment_string);
		}
		fprintf(f,"%s\n",buf);
	}
	fclose(f);
	visdelay2_end();
	free(counts);

	dataopen(temp_fname,0xfff);
}   /* category_make_list */





void category_submenu(menu m1, int entry, FOLDREC *fr, int ix)
/************************************************************/
{
	CAT *catptr;
	menu m2;
	char *name2;

	do {
		entry++;
		catptr = &fr->cat_data[ix];
		menu_extend(m1 ,name2 = &fr->cat_chars[catptr->name]);

		if(catptr->down != 0)
		{
			m2 = menu_new(name2,name2);  /* repeat the parent as the first name */
			category_submenu(m2, 1, fr, catptr->down);
			menu_submenu(m1,entry,m2);
		}

	} while((ix = catptr->right) != 0);

}   /* end of category_submenu */




wimp_menustr *category_make_menu(FOLDREC *fr, int control)
/********************************************************/
/* Control: bit 0: 1=start with "None" */
{
	fr_menu_category = fr;

	if(menu_category != NULL)
		menu_dispose(&menu_category,1);

	menu_category = menu_new(fr->name,"");

	if(control & 1)
	{
		menu_extend(menu_category,"(none)");
	}

	if(fr->cat_data[0].right)
	{
		category_submenu(menu_category,control & 1,fr,fr->cat_data[0].right);
	}
	else
	{
		if((control & 1)==0)
			menu_extend(menu_category,"(none)");
	}

	return((wimp_menustr *)menu_syshandle(menu_category));
}   /* end of category_make_menu */




int category_menu_hit(int *hits, char **cat_name)
/***********************************************/
{
	int  value;
	int  ix;
	int  down;
	FOLDREC *fr;

	if(menu_category != NULL)
	{
		menu_dispose(&menu_category,1);
		menu_category = NULL;
	}

	fr = fr_menu_category;
	ix = fr->cat_data[0].right;
	if((fr->cat_header.n_cats == 0) || (ix == 0))
	{
		if(cat_name != NULL)
			*cat_name = NULL;
		return(0);
	}

	value = *hits++;
	for(;;)
	{
		while(--value >= 0)
		{
			ix = fr->cat_data[ix].right;
		}

		down = fr->cat_data[ix].down;
		if((down == 0) || (*hits <= 0))
			break;;
		ix = down;

		value = (*hits++)-1;
	}

	if(cat_name != NULL)
		*cat_name = category_name(fr_menu_category,ix);

	return(ix);
}   /* end of category_menu_hit */




void category_menu_proc(void *handle, char *hit)
/**********************************************/
{
	int  cat;
	FOLDREC *fr;
	fr = (FOLDREC *)handle;

	switch(hit[0])
	{
	case 1:     /* add new category */
		category_new(fr,0,fr->cursor_ref,fr->cursor_level);
		set_cats_extent(fr,1);
		redraw_cats_lines(fr,0);
		save_catfile(fr,1);
		break;

	case 2:    /* add child */
		category_new(fr,1,fr->cursor_ref,fr->cursor_level);
		set_cats_extent(fr,1);
		redraw_cats_lines(fr,0);
		save_catfile(fr,1);
		break;

	case 3:    /* remove */
		category_remove(fr,0,fr->cursor_ref,fr->cursor_level);
		redraw_cats_lines(fr,0);
		set_cats_extent(fr,1);
		save_catfile(fr,0);
		break;

	case 4:    /* edit */
		category_edit(fr,&fr->cat_data[fr->cursor_ref]);
		redraw_cats_lines(fr,0);
		save_catfile(fr,0);
		break;

	case 5:   /* sort */
		cat = category_find_parent(fr,fr->cursor_ref);
		if(cat >= 0)
		{
			category_sort(fr,cat);
			redraw_cats_lines(fr,0);
			save_catfile(fr,0);
		}
		set_cats_extent(fr,1);
		break;

	case 6:  /* find */
		category_search(fr,0);
		break;

	case 7:   /* list */
		category_make_list(fr);
		break;
	}
}   /* end of category_menu_proc */




void init_cats()
/**************/
{
	wimp_wind *window_template;
	menu m_categories;
	FOLDREC *fr;

	fr = &cty_fr;
	fr->type = CATS;
	strcpy(fr->name,"Categories");
	window_template = template_syshandle("Cats");
	wimp_create_wind(window_template ,&fr->window);
	win_register_event_handler(fr->window,cats_window_handler,(void *)fr);
	memcpy(&fr->workarea.box,&window_template->ex,sizeof(wimp_box));

	m_categories = menu_new(fr->name,menustr_cats);
	event_attachmenu(fr->window,m_categories,category_menu_proc,(void *)fr);
	load_cats_file(fr);


	fr = &src_fr;
	fr->type = SRCS;
	strcpy(fr->name,"Sources");
	window_template = template_syshandle("Srcs");
	wimp_create_wind(window_template ,&fr->window);
	win_register_event_handler(fr->window,cats_window_handler,(void *)fr);
	memcpy(&fr->workarea.box,&window_template->ex,sizeof(wimp_box));

	m_categories = menu_new(fr->name,menustr_cats);
	event_attachmenu(fr->window,m_categories,category_menu_proc,(void *)fr);

	load_cats_file(fr);

}   /* end of init_cats */



