/*							       	     HTNDir.c
**	GENERIC NEWS LISTINGS
**
**	(c) COPYRIGHT MIT 1995.
**	Please first read the full copyright statement in the file COPYRIGH.
**	@(#) $Id: HTNDir.c,v 2.11 1998/05/04 19:37:03 frystyk Exp $
**
**	Creates listings for all kind of News output.
**
** Authors:
**	HF	Henrik Frystyk, MIT, <frystyk@w3.org>
**	MP	Maciej Puzio <puzio@zodiac1.mimuw.edu.pl>
** History:
**	   Oct 95  HFN	written
**	   Mar 96  MP	modified heavily     
*/

/* Library include files */
#include "wwwsys.h"
#include "WWWUtil.h"
#include "WWWCore.h"
#include "WWWHTML.h"
#include "HTNews.h"
#include "HTNewsLs.h"
#include "HTNDir.h"					 /* Implemented here */

/* Macros and other defines */
#define PUTC(c)		(*target->isa->put_character)(target, c)
#define PUTS(s)		(*target->isa->put_string)(target, s)
#define START(e)	(*target->isa->start_element)(target, e, 0, 0)
#define END(e)		(*target->isa->end_element)(target, e)
#define FREE_TARGET	(*target->isa->_free)(target)

#define DEFAULT_MAXW	80			       /* Default line width */

/* Type definitions and global variables etc. local to this module */
struct _HTStructured {
    const HTStructuredClass *	isa;
    /* ... */
};

struct _HTNewsNode {
    int		index;
    char *	name;
    char *	subject;
    char *	from;
    time_t	date;
    int		refs;			 /* Number of references */
    BOOL	is_tmplate;

    /* Added by MP: */
    HTList* refNames;        /*  referee names list */
    HTList* refObjects;      /*  referee objects list */
    HTNewsNode * refParent;  /* direct parent (referee) */
    HTNewsNode * lastChild;  /* last child (referer) */
    BOOL    show;            /* show this node on the list? */
    BOOL    fake;            /* create anchor with this node? */
    int     refChildren;     /* number of referers */
    int     refLevel;        /* reference level - to speed sorting */
    int     minRefIndex;     /* minimal index in reference subtree */
    int     maxRefIndex;     /* maximal index in reference subtree */
    time_t  minRefDate;      /* minimal date in reference subtree */
    time_t  maxRefDate;      /* maximal date in reference subtree */
};

struct _HTNewsDir {
    HTStructured *	target;
    HTRequest *		request;
    HTNewsDirKey	key;				  /* Key for sorting */
    char *   	        name;        /* Name of the newsgroup(s) Added by MP */
    char *		tmplate;
    HTNewsNode *	tmplate_node;
    int 	        lastLevel;    /* Last printed ref level Added by MP. */
    HTArray *		array;			/* Array for sorted listings */
    HTArray *		cache;			  /* Only created on request */
};

PRIVATE int MaxLineW = DEFAULT_MAXW;

/*  Forward references - added by MP. */
PRIVATE void HTNewsDir_addLevelTags (HTNewsDir* dir, int level);
PRIVATE HTNewsNode* HTNewsDir_addFakeElement (HTNewsDir* dir, char* subject,
    char* name);
PRIVATE HTNewsNode* HTNewsDir_findNodeNamed (HTNewsDir* dir, char* name);

#define FNWS_ANY       0x0000
/* Date */
#define FNWS_MIN       0x0001
#define FNWS_MAX       0x0002
/* Fake/not-fake */
#define FNWS_ONLYFAKE  0x0010
#define FNWS_NOTFAKE   0x0020
/* Fake or not-fake with references */
#define FNWS_NOTORPHAN 0x0040
PRIVATE HTNewsNode* HTNewsDir_findNodeWithSubject (HTNewsDir* dir, 
    char* subject, int which, HTNewsNode* avoidNode);


/* ------------------------------------------------------------------------- */
/*				NODE  MANAGEMENT 			     */
/* ------------------------------------------------------------------------- */

PRIVATE HTNewsNode * HTNewsNode_new (int index, char * subject, char * from,
				     time_t date, char * name,
				     int refs, HTList * refNames)
{
    HTNewsNode * node;
    if ((node = (HTNewsNode *) HT_CALLOC(1, sizeof(HTNewsNode))) == NULL)
        HT_OUTOFMEM("HTNewsNode_new");
    if (name) StrAllocCopy(node->name, name);
    if (subject) {
	StrAllocCopy(node->subject, subject);
	node->subject = HTStrip(node->subject);
    }
    if (from) StrAllocCopy(node->from, from);
    node->index = index;
    node->date = date;
    node->refs = refs;
    node->refNames = refNames;
    node->show = (name != NULL);
    node->fake = (name == NULL);
    node->minRefIndex = index;
    node->maxRefIndex = index;
    node->minRefDate = date;
    node->maxRefDate = date;
    return node;
}

PRIVATE BOOL HTNewsNode_delete (HTNewsNode * node, BOOL cache)
{
    if (node) {
    	if (!cache || node->is_tmplate) HT_FREE(node->name);
	HT_FREE(node->subject);
	HT_FREE(node->from);
        if (node->refNames) {
	    HTList * cur = node->refNames;
	    char * pres;	    
            while ((pres = (char *) HTList_nextObject(cur))) HT_FREE(pres);
            HTList_delete(node->refNames);
        }
        if (node->refObjects) HTList_delete(node->refObjects);
	HT_FREE(node);
	return YES;
    }
    return NO;
}

/* Added by MP. */
PRIVATE BOOL HTNewsNode_isAncestor (HTNewsNode* node, HTNewsNode* refered)
{
    HTNewsNode* p;
    for (p = refered; p; p = p->refParent)
        if (p == node) return YES;
    return NO;
}

/* Added by MP. */
PRIVATE BOOL HTNewsNode_linkRef (HTNewsNode* node, HTNewsNode* referer)
{
    if (node && referer) {
	node->refChildren++;
	node->lastChild = referer;
	node->minRefIndex = (node->minRefIndex != 0 ? 
			     HTMIN(node->minRefIndex, referer->index) :
			     referer->index);
	node->maxRefIndex = (node->maxRefIndex != 0 ? 
			     HTMAX(node->maxRefIndex, referer->index) :
			     referer->index);
	node->minRefDate = (node->minRefDate != 0 ? 
			    HTMIN(node->minRefDate, referer->date) :
			    referer->date);
	node->maxRefDate = (node->maxRefDate != 0 ? 
			    HTMAX(node->maxRefDate, referer->date) :
			    referer->date);
	referer->refParent = node;
	return YES;
    }
    return NO;
}

/* Added by MP. */
PRIVATE int HTNewsNode_refLevel (HTNewsNode* node)
{
    int cnt = 0;
    HTNewsNode* p;
    for (p = node->refParent; p && p->show; p = p->refParent)
        cnt++;
    return cnt;
}

/* Added by MP. */
/* Returns index field for non-fake nodes, minimal or maximal subtreee */
/* index for fake nodes. */
PRIVATE time_t HTNewsNode_getIndex (HTNewsNode* node, BOOL minimal)
{
    if (node->index != 0)
        return node->index;
    else if (minimal)
        return node->minRefIndex;
    else
        return node->maxRefIndex;
}

/* Added by MP. */
/* Returns date field for non-fake nodes, minimal or maximal subtreee */
/* date for fake nodes. */
PRIVATE time_t HTNewsNode_getDate (HTNewsNode* node, BOOL minimal)
{
    if (node->date != 0)
        return node->date;
    else if (minimal)
        return node->minRefDate;
    else
        return node->maxRefDate;
}

/* Helper function - added by MP. */
PRIVATE char* UnReSubject (char* subject)
{
    if (strlen(subject) >= 3 && strncasecomp(subject, "re:", 3) == 0)
    {
        char* p = subject + 3;  /* "Re:XXX" */
        if (*p == ' ')  p ++;   /* "Re: XXX" */
        return p;
    }
    return subject;
}

/* Added by MP. */
PRIVATE void HTNewsNode_setRefInfo_pass1 (HTNewsDir* dir, HTNewsNode* node)
{
    HTList* ptr = node->refNames;
    char* name = NULL;
    if (node->fake)
        return;
    if (ptr != NULL)
        name = (char*) HTList_nextObject(ptr);
    while (ptr != NULL)
    {
        HTNewsNode* parent;
        parent = HTNewsDir_findNodeNamed(dir, name);
        if (parent)
        {
            if (!node->refObjects)  node->refObjects = HTList_new();
            HTList_addObject (node->refObjects, parent);
        }
        name = (char*) HTList_nextObject(ptr);
    }
}

/* Added by MP. */
PRIVATE void HTNewsNode_setRefInfo_pass2 (HTNewsDir* dir, HTNewsNode* node)
{
    HTNewsNode* maxParent = NULL;
    HTList* ptr = node->refObjects;
    HTNewsNode* parent = NULL;
    if (node->fake)
        return;
    if (ptr != NULL)
        parent = (HTNewsNode*) HTList_nextObject(ptr);
    while (ptr != NULL)
    {
        if (!maxParent || maxParent->date < parent->date)
            maxParent = parent;
        parent = (HTNewsNode*) HTList_nextObject(ptr);
    }
    if (maxParent)
    {
        if (!HTNewsNode_isAncestor(node, maxParent))  /* better be careful */
            HTNewsNode_linkRef (maxParent, node);
    }
    else
    {
        char* refSubject;
        BOOL re;

        /* Here is the only place we REALLY have to check for circular */
        /* references. It is normally possible that a node refers to */
        /* orphan node and both have the same subject. In this situation */
        /* we can't make the orphan to refer to it's child. Without checking */
        /* for circular references this is likely to happen here. */

        refSubject = UnReSubject(node->subject);
        re = (strcasecomp(refSubject, node->subject) != 0);
        if (re)
            parent = HTNewsDir_findNodeWithSubject(dir, refSubject, 
                FNWS_MIN | FNWS_NOTFAKE, node);
        if (!parent || HTNewsNode_isAncestor(node, parent))
            parent = HTNewsDir_findNodeWithSubject(dir, refSubject,
                FNWS_MIN | FNWS_ONLYFAKE, node);
        if (!parent && re)
        {
            parent = HTNewsDir_findNodeWithSubject(dir, node->subject, 
                FNWS_MIN | FNWS_ONLYFAKE, node);
        }
    
        if (!parent) parent = HTNewsDir_addFakeElement (dir, refSubject, NULL);
	if (parent) {
	    HTNewsNode_linkRef (parent, node);
	    if (parent->refChildren > 1) /* Multi-children fake node visible */
		parent->show = YES;
	}
    }
}

/*
** Added by MP.
*/
#if 0
PRIVATE void HTNewsNode_setRefInfo_pass3 (HTNewsDir* dir, HTNewsNode* node)
{
    HTNewsNode* parent = NULL;
    char* refSubject;
    BOOL re;

    if (node->fake || node->refParent)
        return;  /* This is only for nodes not handled in pass 2 */
    
    /* Here is the only place we REALLY have to check for circular */
    /* references. It is normally possible that a node refers to */
    /* orphan node and both have the same subject. In this situation */
    /* we can't make the orphan to refer to it's child. Without checking */
    /* for circular references this is likely to happen here. */

    refSubject = UnReSubject(node->subject);
    re = (strcasecomp(refSubject, node->subject) != 0);
    if (re)
        parent = HTNewsDir_findNodeWithSubject(dir, refSubject, 
            FNWS_MIN | FNWS_NOTFAKE, node);
    if (!parent || HTNewsNode_isAncestor(node, parent))
        parent = HTNewsDir_findNodeWithSubject(dir, refSubject,
            FNWS_MIN | FNWS_ONLYFAKE, node);
    if (!parent && re)
    {
        parent = HTNewsDir_findNodeWithSubject(dir, node->subject, 
            FNWS_MIN | FNWS_ONLYFAKE, node);
    }
    
    if (!parent) parent = HTNewsDir_addFakeElement (dir, refSubject, NULL);
    if (parent) {
	HTNewsNode_linkRef (parent, node);
	if (parent->refChildren > 1)  	 /* multi-children fake node visible */
	    parent->show = YES;
    }
}
#else
PRIVATE void HTNewsNode_setRefInfo_pass3 (HTNewsDir* dir, HTNewsNode* node)
{
    if (!node->fake)
        node->refLevel = HTNewsNode_refLevel(node);
}
#endif

/* Added by MP. */
PRIVATE int HTNewsNode_compareRefThread (HTNewsNode* node1, HTNewsNode* node2)
{
    int level1 = node1->refLevel;
    int level2 = node2->refLevel;
    int level = HTMAX(level1, level2);
    int i;
    HTNewsNode* parent1;
    HTNewsNode* parent2;
    int diff = 0;
    for (i = level; i >= 0; i--)
    {
        parent1 = (i < level1 ? parent1->refParent : node1);
        parent2 = (i < level2 ? parent2->refParent : node2);
        if (parent1 == parent2)
            return diff;  /* related messages (in same subtree) */
        else
        {
            time_t date1 = (i > level1 ? 0 : HTNewsNode_getDate(parent1, YES));
            time_t date2 = (i > level2 ? 0 : HTNewsNode_getDate(parent2, YES));
            diff = date1 - date2;
            if (diff == 0)
            {
                int idx1 = (i > level1 ? 0 : HTNewsNode_getIndex(parent1, YES));
                int idx2 = (i > level2 ? 0 : HTNewsNode_getIndex(parent2, YES));
                diff = idx1 - idx2;
            }
        }
    }
    return diff;  /* completely unrelated messages */
}

/*
**	Output an element in HTML
**	Returns YES if OK, else NO
*/
PRIVATE BOOL HTNewsNode_print (HTNewsDir * dir, HTNewsNode * node)
{
    if (node && node->show) {
	HTStructured *target = dir->target;
	char  * escaped;
    
	HTNewsDir_addLevelTags (dir, node->refLevel);  /* Added by MP. */
	START(HTML_LI);

	/* Start the anchor and put the subject as anchor text */
	/*  Changed by MP to allow nodes without names */
	if (!node->fake && node->name && node->subject)  {
	    escaped = HTEscape(node->name, URL_XPALPHAS);
	    HTStartAnchor(target, NULL, escaped);
	}
	if (node->subject) PUTS(node->subject);
	if (!node->fake && node->name && node->subject) {
	    END(HTML_A);
	    HT_FREE(escaped);
	}

	/* From field */
	if (node->from)  {
	    PUTS (" by ");  /* Changed by MP. */
	    PUTS(node->from);
	}

	/*  In group listing, put number of groups in the set; added by MP. */
	if (node->name && strrchr(node->name, '*')) {
	    char buf[16];
	    sprintf (buf, " (%d groups)", node->refChildren);
	    PUTS (buf);
	}
    }
    return YES;
}

/* ------------------------------------------------------------------------- */
/*				DIRECTORY MANAGEMENT 			     */
/* ------------------------------------------------------------------------- */

/*	HTNewsDir_setWidth
**	------------------
**	The module automatically ajusts the width of the directory listing as
**	a function of the file name. The width can flows dynamically between
**	an upper and a lower limit.
*/
PUBLIC BOOL HTNewsDir_setWidth (int max_width)
{
    MaxLineW = (max_width > 0) ? max_width : DEFAULT_MAXW;
    return YES;
}

/*	HTNewsDir_new
**	----------
**    	Creates a structured stream object and sets up the initial HTML stuff
**	Returns the newsdir object if OK, else NULL
*/
PUBLIC HTNewsDir * HTNewsDir_new (HTRequest * request, const char * title,
				  HTNewsDirKey key, BOOL cache)
{
    HTNewsDir *dir;
    if (!request) return NULL;

    /* Create object */
    if ((dir = (HTNewsDir *) HT_CALLOC(1, sizeof (HTNewsDir))) == NULL)
        HT_OUTOFMEM("HTNewsDir_new");
    dir->target = HTMLGenerator(request, NULL, WWW_HTML,
				HTRequest_outputFormat(request),
				HTRequest_outputStream(request));
    HTAnchor_setFormat(HTRequest_anchor(request), WWW_HTML);
    dir->request = request;
    dir->key = key;
    dir->lastLevel = -1;  /* Added by MP. */

    /*  Get the newsgroup(s) name; added by MP. */
    {
        char* url = HTAnchor_physical(HTRequest_anchor(request));
        char* p = url+strlen(url);
        while (p > url && p[-1] != ':' && p[-1] != '/' && p[-1] != '\\')
            p--;
        StrAllocCopy (dir->name, p);
    }

    if (key != HT_NDK_NONE) {			       /* Thread is unsorted */
	int total = HTNews_maxArticles();
	dir->array = HTArray_new(total > 0 ? total : 128);
    }

    /* If we are asked to prepare a cache entry then create the cache array */
    if (cache) {
	int total = HTNews_maxArticles();
	dir->cache = HTArray_new(total > 0 ? total : 128);
    }

    /* Start the HTML stuff */
    {
	HTStructured *target = dir->target;
	const char *msg = title ? title : "News Listing";
	START(HTML_HTML);
	START(HTML_HEAD);
	START(HTML_TITLE);
	PUTS(msg);
	END(HTML_TITLE);
	END(HTML_HEAD);
	START(HTML_BODY);
	START(HTML_H1);
	PUTS(msg);
	END(HTML_H1);
    }
    return dir;
}

/*	HTNewsDir_addElement
**	--------------------
**    	This function accepts a news line. Everything except dir and name can
**	can be 0 or NULL.
**	Returns new node pointer if OK, else NULL
**	Changed by MP: reference list added.
**	Note: Unlike other parameters, refNames is not copied, but assigned, so
**	it has to contain copies of message names, not the originals.
*/
PUBLIC HTNewsNode* HTNewsDir_addElement (HTNewsDir * dir, int index, 
					 char * subject, char * from,
					 time_t date, char * name,
					 int refs, HTList * refNames)
{
    if (dir && name) {
	HTNewsNode * node = HTNewsNode_new(index, subject, from,
					   date, name, refs, refNames);
	if (dir->key == HT_NDK_NONE) {
	    HTNewsNode_print(dir, node);
	    HTNewsNode_delete(node, (dir->cache!=NULL));
	} else
	    HTArray_addObject(dir->array, (void *) node);
	return node;
    }
    return NULL;
}

/* Helper function - added by MP. */
PRIVATE HTNewsNode* HTNewsDir_addFakeElement (HTNewsDir * dir, 
					      char * subject, char * name)
{
    HTNewsNode * node =
	HTNewsDir_addElement(dir, 0, subject, NULL, 0, name, 0, NULL);
    if (node) {
	node->show = NO;
	node->fake = YES;
    }
    return node;
}

/* Helper function - added by MP. */
PUBLIC HTNewsNode * HTNewsDir_addGroupElement (HTNewsDir * dir, char * group,
					       BOOL tmplate)
{
    HTNewsNode * node = NULL;
    if (dir && group) {
	if (HTNewsDir_belongsToSet(dir, group))
	    node=HTNewsDir_addElement (dir, 0, group, NULL, 0, group, 0, NULL);

	/* If we are building a cache object then add the entry */
	if (dir->cache && !tmplate) {
	    char * name = node ? node->name : NULL;
	    if (!name) StrAllocCopy(name, group);
	    HTArray_addObject(dir->cache, name);
	}
    }
    return node;
}

/* Added by MP. */
PUBLIC BOOL HTNewsDir_belongsToSet (HTNewsDir* dir, char* group)
{
    char* p;
    if (!dir->name || !*(dir->name))
        return YES;
    p = strrchr(dir->name, '*');
    if (!p)
        return strcasecomp(group, dir->name) == 0;
    else
    {
        int len = p - dir->name;
        return strncasecomp(group, dir->name, len) == 0;
    }
}


/* Added by MP. */
PRIVATE void HTNewsDir_addLevelTags (HTNewsDir* dir, int level)
{
    HTStructured *target = dir->target;
    int i = level;
    while (i > dir->lastLevel)
    {
        START(HTML_UL);
        i--;
    }
    while (i < dir->lastLevel)
    {
        END(HTML_UL);
        i++;
    }
    dir->lastLevel = level;
}

/* Added by MP. */
PRIVATE HTNewsNode* HTNewsDir_findNodeNamed (HTNewsDir* dir, char* name)
{
    int i;
    for (i = 0; i < HTArray_size(dir->array); i++)
    {
        HTNewsNode* node = (HTNewsNode*)(HTArray_data(dir->array)[i]);
        if (node->name && strcasecomp(node->name, name) == 0)
            return node;
    }
    return NULL;
}

/* Added by MP. */
PRIVATE HTNewsNode* HTNewsDir_findNodeWithSubject (HTNewsDir* dir, 
    char* subject, int which, HTNewsNode* avoidNode)
{
    int i;
    int whichDate = (which & FNWS_MIN ? -1 : (which & FNWS_MAX ? 1 : 0));
    HTNewsNode* foundNode = NULL;
    for (i = 0; i < HTArray_size(dir->array); i++)
    {
        HTNewsNode* node = (HTNewsNode*)(HTArray_data(dir->array)[i]);
        if (!(which & FNWS_ONLYFAKE && !node->fake) && 
            !(which & FNWS_NOTFAKE && node->fake) &&
            !(which & FNWS_NOTORPHAN && !node->fake && !node->refNames) &&
            node != avoidNode && node->subject && 
            strcasecomp(node->subject, subject) == 0)
        {
            if (which == FNWS_ANY)
                return node;
            else if (!foundNode || (node->date != 0 && 
                (node->date - foundNode->date) * (long)whichDate > 0))
                foundNode = node;
        }                  
    }
    return foundNode;
}

/* Added by MP. */
PRIVATE void HTNewsDir_setRefInfo (HTNewsDir* dir)
{
    /* Array grows when fake elements are added.  */
    /* We don't want to set reference info for fake elements. */
    int size = HTArray_size(dir->array);
    int i;
    for (i = 0; i < size; i++)
        HTNewsNode_setRefInfo_pass1 (dir, (HTNewsNode*)(HTArray_data(dir->array)[i]));
    for (i = 0; i < size; i++)
        HTNewsNode_setRefInfo_pass2 (dir, (HTNewsNode*)(HTArray_data(dir->array)[i]));
    for (i = 0; i < size; i++)
        HTNewsNode_setRefInfo_pass3 (dir, (HTNewsNode*)(HTArray_data(dir->array)[i]));
}

PRIVATE void make_template (HTNewsDir * dir, HTNewsNode * node)
{
    HT_FREE(dir->tmplate);
    if ((dir->tmplate = (char *) HT_MALLOC(strlen(node->name) + 3)) == NULL)
	HT_OUTOFMEM("HTNewsNode_setGroupInfo");
    {
	char * p1 = dir->name;
	char * p2 = dir->tmplate;
	strcpy(p2, node->name);
	while (*p1 && *p2 && *p1 == *p2)  p1++, p2++;
	while (*p2 && *p2 != '.') p2++;
	if (*p2) {
	    strcpy(p2, ".*");
	    dir->tmplate_node=HTNewsDir_addGroupElement(dir, dir->tmplate,YES);
	    dir->tmplate_node->is_tmplate = YES;
	} else {
	    HT_FREE(dir->tmplate);
	    dir->tmplate_node = node;
	}
	dir->tmplate_node->show = YES;
    }
}

/*
**	Runs through a sorted list of news groups and identifies the group
**	hierarchy. Template groups are added to the list, for example as
**	"alt.*"
*/
PRIVATE void HTNewsDir_setGroupInfo (HTNewsDir * dir)
{
    HTArray * array = dir->array;
    HTNewsNode * node;
    int cur_size = HTArray_size(array);
    int cnt;

    /*
    ** If we don't have a template to test against then create one
    ** A template can be something like "alt.*" for example
    */
    for (cnt=0; cnt<cur_size; cnt++) {
	node = (HTNewsNode *) HTArray_data(array)[cnt];

	/*
	** Make a template if we don't have any
	*/
	if (!dir->tmplate) make_template(dir, node);
    
	/*
	** Now, if we do have a template then test the node name against
	** it to see if we have this group already or it is a new group
	** at this level in the hierarchy
	*/
	if (dir->tmplate) {
	    if (HTStrCaseMatch(dir->tmplate, node->name) == NULL) {
		make_template(dir, node);
	    } else {
		HTNewsNode * tmp_node = dir->tmplate_node;
		
		/* Should we show this element in the list? */
		if (tmp_node->lastChild) {
		    tmp_node->lastChild->show = NO;
		    node->show = NO;
		}
	    }
	    HTNewsNode_linkRef(dir->tmplate_node, node);
	}
    }
}

PRIVATE int NDirIndexSort (const void *a, const void *b)
{
    int aa = (*((HTNewsNode **)a))->index;
    int bb = (*((HTNewsNode **)b))->index;
    return aa-bb;
}

PRIVATE int NDirSubjectSort (const void *a, const void *b)
{
    char *aa = (*((HTNewsNode **)a))->subject;
    char *bb = (*((HTNewsNode **)b))->subject;
    return strcasecomp(aa?aa:"", bb?bb:"");
}

PRIVATE int NDirFromSort (const void *a, const void *b)
{
    char *aa = (*((HTNewsNode **)a))->from;
    char *bb = (*((HTNewsNode **)b))->from;
    return strcasecomp(aa?aa:"", bb?bb:"");
}

PRIVATE int NDirDateSort (const void *a, const void *b)
{
    time_t aa = (*((HTNewsNode **)a))->date;
    time_t bb = (*((HTNewsNode **)b))->date;
    return bb-aa;
}

PRIVATE int NDirGroupSort (const void *a, const void *b)
{
    char *aa = (*((HTNewsNode **)a))->name;
    char *bb = (*((HTNewsNode **)b))->name;
    while (*aa && *bb && TOLOWER(*aa)==TOLOWER(*bb)) aa++, bb++;
    return (*aa=='.' && *bb) ? -1 : (*aa && *bb=='.') ?
	1 : TOLOWER(*aa)-TOLOWER(*bb);
}

/* Added by MP. */
PRIVATE int NDirRefThreadSort (const void* a, const void* b)
{
    HTNewsNode* aa = *((HTNewsNode**)a);
    HTNewsNode* bb = *((HTNewsNode**)b);
    return HTNewsNode_compareRefThread(aa,bb);
}

/*	HTNewsDir_free
**	--------------
**    	If we are sorting then do the sorting and put out the list,
**	else just append the end of the list.
*/
PUBLIC BOOL HTNewsDir_free (HTNewsDir * dir)
{
    if (!dir) return NO;
    if (dir->key != HT_NDK_NONE) {
    	HTArray * array = dir->array;
	HTArray * cache = NULL;
    	HTComparer * comp = NULL;

	/*
	** Find a suitable sort key for this listing. The sort function
	** depends on the type of new listing we have received.
	*/
    	if (dir->key == HT_NDK_INDEX)	           /* Sort by Message Number */
    	    comp = NDirIndexSort;
    	else if (dir->key == HT_NDK_DATE)	             /* Sort by Date */
    	    comp = NDirDateSort;
    	else if (dir->key == HT_NDK_SUBJECT)           /* Sort after Subject */
    	    comp = NDirSubjectSort;         
    	else if (dir->key == HT_NDK_FROM)		  /* Sort after From */
    	    comp = NDirFromSort;
    	else if (dir->key == HT_NDK_GROUP) {	  /* Sort as group hierarchi */
	    comp = NDirGroupSort;
        } else if (dir->key == HT_NDK_REFTHREAD) {    /* Added by MP. */
            HTNewsDir_setRefInfo (dir);
            comp = NDirRefThreadSort;
        } else {
    	    if (STREAM_TRACE) HTTrace("NewsListing. Invalid sortkey\n");
    	    return NO;
    	}

	/*
	** Now sort the array of news items that we have read with the sort
	** function defined by the sort key above.
	*/
    	HTArray_sort(array, comp);

	/*
	** If we are showing a group listing then run through the list and
	** identify group hierarchy. We have to sort the thing again in order
	** to get the new template groups included
	*/
	if (dir->key == HT_NDK_GROUP) {
	    HTNewsDir_setGroupInfo(dir);
	    HTArray_sort(array, comp);
	}
	
	/*
	** After we have sorted the listing, we can write out the result and
	** free the array.
	*/
    	{
    	    void ** data;
    	    HTNewsNode *node = (HTNewsNode *) HTArray_firstObject(array, data);
    	    while (node) {
		HTNewsNode_print(dir, node);

		/*
		** Create a special array for the cache containing the group
		** names only and no templates
		*/
		if (dir->key == HT_NDK_GROUP && !node->is_tmplate)
		    HTArray_addObject(cache, node->name);

		HTNewsNode_delete(node, (dir->cache!=NULL));
		node = (HTNewsNode *) HTArray_nextObject(array, data);
    	    }
	    HTArray_delete(array);	
	}

	/* Update the cache */
	if (dir->cache) HTNewsCache_after(dir->request, NULL, dir->cache, 0);
    }

    /* Put out the end of the HTML stuff */
    {
	HTStructured *target = dir->target;
	/* END(HTML_UL); */
        HTNewsDir_addLevelTags (dir, -1);
	START(HTML_HR);
	END(HTML_BODY);
	END(HTML_HTML);
	FREE_TARGET;
    }

    /* Clean up the dir object */
    HT_FREE(dir->name);
    HT_FREE(dir->tmplate);
    HT_FREE(dir);
    return YES;
}
