/*
** Copyright 2000-2009 Double Precision, Inc.
** See COPYING for distribution information.
*/


#include	"config.h"
#include	"imapd.h"
#include	"thread.h"
#include	"searchinfo.h"
#include	<stdio.h>
#include	<string.h>
#include	<stdlib.h>
#include	"imapwrite.h"
#include	"imaptoken.h"
#include	"imapscanclient.h"
#include	"rfc822/rfc822.h"
#include	"rfc822/rfc2047.h"
#include	"rfc822/imaprefs.h"
#include	"unicode/unicode.h"

static void thread_os_callback(struct searchinfo *, struct searchinfo *, int,
	unsigned long, void *);
static void thread_ref_callback(struct searchinfo *, struct searchinfo *, int,
	unsigned long, void *);

extern struct imapscaninfo current_maildir_info;


struct os_threadinfo {
	struct os_threadinfo *next;
	char *subj;
	time_t sent_date;
	unsigned long n;
	} ;

struct os_threadinfo_list {
	struct os_threadinfo_list *next;
	size_t thread_start;
} ;

struct os_struct {
	struct os_threadinfo *list;
	unsigned nmsgs;
	struct os_threadinfo **msgs;
	} ;

static void os_init(struct os_struct *os)
{
	memset(os, 0, sizeof(*os));
}

static void os_add(struct os_struct *os, unsigned long n, const char *s,
		   time_t sent_date)
{
struct os_threadinfo *osi=(struct os_threadinfo *)
	malloc(sizeof(struct os_threadinfo));

	if (!osi)	write_error_exit(0);
	osi->subj=strdup(s);
			/* This decodes the MIME encoding */
	if (!osi->subj)	write_error_exit(0);
	osi->sent_date=sent_date;
	osi->n=n;
	osi->next=os->list;
	os->list=osi;
	++os->nmsgs;
}

static void os_free(struct os_struct *os)
{
struct os_threadinfo *p;

	while ((p=os->list) != 0)
	{
		os->list=p->next;
		free(p->subj);
		free(p);
	}
	if (os->msgs) free(os->msgs);
}

static int cmpsubjs(const void *a, const void *b)
{
	const struct os_threadinfo *ap=*(const struct os_threadinfo **)a;
	const struct os_threadinfo *bp=*(const struct os_threadinfo **)b;
	int rc=strcmp( ap->subj, bp->subj);

	if (rc)	return (rc);

	return (ap->sent_date < bp->sent_date ? -1:
		ap->sent_date > bp->sent_date ? 1:0);
}

/* Print the meat of the THREAD ORDEREDSUBJECT response */

static void printos(struct os_threadinfo **array, size_t cnt)
{
	size_t	i;
	struct os_threadinfo_list *thread_list=NULL, *threadptr, **tptr;

	/*
	** thread_list - indexes to start of each thread, sort indexes by
	** sent_date
	*/

	for (i=0; i<cnt; i++)
	{
		/* Find start of next thread */

		if (i > 0 && strcmp(array[i-1]->subj, array[i]->subj) == 0)
			continue;

		threadptr=malloc(sizeof(struct os_threadinfo_list));
		if (!threadptr)
			write_error_exit(0);
		threadptr->thread_start=i;

		/* Insert into the list, sorted by sent date */

		for (tptr= &thread_list; *tptr; tptr=&(*tptr)->next)
			if ( array[(*tptr)->thread_start]->sent_date
			     > array[i]->sent_date)
				break;

		threadptr->next= *tptr;
		*tptr=threadptr;
	}

	while ( (threadptr=thread_list) != NULL)
	{
		size_t	i, j;
		const char *p;

		thread_list=threadptr->next;

		i=threadptr->thread_start;
		free(threadptr);

		for (j=i+1; j<cnt; j++)
		{
			if (strcmp(array[i]->subj, array[j]->subj))
				break;
		}

		p="(";
		while (i < j)
		{
			writes(p);
			p=" ";
			writen(array[i]->n);
			++i;
		}
		writes(")");
	}
}

void dothreadorderedsubj(struct searchinfo *si, struct searchinfo *sihead,
			 const char *charset, int isuid)
{
struct	os_struct	os;

	os_init(&os);
	search_internal(si, sihead, charset, isuid, thread_os_callback, &os);

	if (os.nmsgs > 0)	/* Found some messages */
	{
	size_t	i;
	struct os_threadinfo *o;

		/* Convert it to an array */

		os.msgs= (struct os_threadinfo **)
			malloc(os.nmsgs * sizeof(struct os_threadinfo *));
		if (!os.msgs)	write_error_exit(0);
		for (o=os.list, i=0; o; o=o->next, i++)
			os.msgs[i]=o;

		/* Sort the array */

		qsort(os.msgs, os.nmsgs, sizeof(*os.msgs), cmpsubjs);

		/* Print the array */

		printos(os.msgs, os.nmsgs);
	}
	os_free(&os);
}

/*
This callback function is called once search finds a qualifying message.
We save its message number and subject in a link list.
*/

static void thread_os_callback(struct searchinfo *si,
			       struct searchinfo *sihead,
			       int isuid, unsigned long i,
			       void *voidarg)
{
	if (sihead->type == search_orderedsubj)
		/* SHOULD BE ALWAYS TRUE */
		os_add( (struct os_struct *)voidarg,
			isuid ? current_maildir_info.msgs[i].uid:i+1,
			sihead->as ? sihead->as:"",
			sihead->bs ? rfc822_parsedt(sihead->bs):0);
}

static void printthread(struct imap_refmsg *, int);

void dothreadreferences(struct searchinfo *si, struct searchinfo *sihead,
			const char *charset,
			int isuid)
{
	struct imap_refmsgtable *reftable;
	struct imap_refmsg *root;

	if (!(reftable=rfc822_threadalloc()))
	{
		write_error_exit(0);
		return;
	}

	search_internal(si, sihead, charset, 0,
			thread_ref_callback, reftable);

	root=rfc822_thread(reftable);
	printthread(root, isuid);
	rfc822_threadfree(reftable);
}

static void thread_ref_callback(struct searchinfo *si,
			       struct searchinfo *sihead,
			       int isuid, unsigned long i,
			       void *voidarg)
{
	if (sihead->type == search_references1 && sihead->a &&
	    sihead->a->type == search_references2 && sihead->a->a &&
	    sihead->a->a->type == search_references3 && sihead->a->a->a &&
	    sihead->a->a->a->type == search_references4)
	{
		const char *ref, *inreplyto, *subject, *date, *msgid;

		ref=sihead->as;
		inreplyto=sihead->bs;
		date=sihead->a->as;
		subject=sihead->a->a->as;
		msgid=sihead->a->a->a->as;

#if 0
		fprintf(stderr, "REFERENCES: ref=%s, inreplyto=%s, subject=%s, date=%s, msgid=%s\n",
			ref ? ref:"",
			inreplyto ? inreplyto:"",
			subject ? subject:"",
			date ? date:"",
			msgid ? msgid:"");
#endif

		if (!rfc822_threadmsg( (struct imap_refmsgtable *)voidarg,
				       msgid, ref && *ref ? ref:inreplyto,
				       subject, date, 0, i))
			write_error_exit(0);
	}
}

static void printthread(struct imap_refmsg *msg, int isuid)
{
	const char *pfix="";

	while (msg)
	{
		if (!msg->isdummy)
		{
			writes(pfix);
			writen(isuid ?
			       current_maildir_info.msgs[msg->seqnum].uid:
			       msg->seqnum+1);
			pfix=" ";
		}

		if (msg->firstchild && (msg->firstchild->nextsib
					|| msg->firstchild->isdummy
					|| msg->parent == NULL))
		{
			writes(pfix);
			for (msg=msg->firstchild; msg; msg=msg->nextsib)
			{
				struct imap_refmsg *msg2;

				msg2=msg;

				if (msg2->isdummy)
					msg2=msg2->firstchild;

				for (; msg2; msg2=msg2->firstchild)
				{
					if (!msg2->isdummy ||
					    msg2->nextsib)
						break;
				}

				if (msg2)
				{
					writes("(");
					printthread(msg, isuid);
					writes(")");
				}
			}
			break;
		}
		msg=msg->firstchild;
	}
}

void free_temp_sort_stack(struct temp_sort_stack *t)
{
	while (t)
	{
	struct temp_sort_stack *u=t->next;

		free(t);
		t=u;
	}
}

/* ---------------------------------- SORT ---------------------------- */

/* sortmsginfo holds the sorting information for a message. */

struct sortmsginfo {
	struct sortmsginfo *next;	/* next msg */
	unsigned long n;		/* msg number/uid */
	char **sortfields;		/* array of sorting fields */
	char *sortorder;		/* [x]=0 - normal, [x]=1 - reversed */
	size_t nfields;
	} ;

struct sortmsgs {
	struct sortmsginfo *list;	/* The actual list */
	struct sortmsginfo **array;	/* In array form */
	size_t	nmsgs;			/* Array size/count of msgs */
	size_t	nfields;		/* From the SORT() arg */
	} ;

static void free_sortmsgs(struct sortmsgs *p)
{
struct sortmsginfo *q;

	if (p->array)	free(p->array);
	while ((q=p->list) != 0)
	{
	size_t	i;

		p->list=q->next;
		for (i=0; i<p->nfields; i++)
			if (q->sortfields[i])
				free(q->sortfields[i]);
		if (q->sortfields)	free(q->sortfields);
		if (q->sortorder)	free(q->sortorder);
		free(q);
	}
}

static void sort_callback(struct searchinfo *, struct searchinfo *, int,
	unsigned long, void *);

static int cmpsort(const void *a, const void *b)
{
const struct sortmsginfo *ap=*(const struct sortmsginfo **)a;
const struct sortmsginfo *bp=*(const struct sortmsginfo **)b;
size_t	i;

	for (i=0; i<ap->nfields; i++)
	{
	int	n=strcmp(ap->sortfields[i], bp->sortfields[i]);

		if (n < 0)
			return (ap->sortorder[i] ? 1:-1);
		if (n > 0)
			return (ap->sortorder[i] ? -1:1);
	}
	return (0);
}

void dosortmsgs(struct searchinfo *si, struct searchinfo *sihead,
		const char *charset, int isuid)
{
struct	sortmsgs sm;
struct	searchinfo *p;

	memset(&sm, 0, sizeof(sm));
	for (p=sihead; p; p=p->a)
		switch (p->type)	{
		case search_orderedsubj:
		case search_arrival:
		case search_cc:
		case search_date:
		case search_from:
		case search_size:
		case search_to:
			++sm.nfields;
			break;
		default:
			break;
		}
	search_internal(si, sihead, charset, isuid, sort_callback, &sm);
	if (sm.nmsgs > 0)
	{
	size_t	i;
	struct sortmsginfo *o;

		/* Convert it to an array */

		sm.array= (struct sortmsginfo **)
			malloc(sm.nmsgs * sizeof(struct sortmsginfo *));
		if (!sm.array)	write_error_exit(0);
		for (o=sm.list, i=0; o; o=o->next, i++)
			sm.array[i]=o;

		/* Sort the array */

		qsort(sm.array, sm.nmsgs, sizeof(*sm.array), cmpsort);

		/* Print the array */

		for (i=0; i<sm.nmsgs; i++)
		{
			writes(" ");
			writen(sm.array[i]->n);
		}
	}
	free_sortmsgs(&sm);
}

static void sort_callback(struct searchinfo *si, struct searchinfo *sihead,
	int isuid, unsigned long n, void *voidarg)
{
struct sortmsgs *sm=(struct sortmsgs *)voidarg;
struct sortmsginfo *msg=(struct sortmsginfo *)
		malloc(sizeof(struct sortmsginfo));
struct searchinfo *ss;
int rev;
size_t	i;

	if (msg)	memset(msg, 0, sizeof(*msg));
	if (!msg || (sm->nfields && ((msg->sortfields=(char **)
				malloc(sizeof(char *)*sm->nfields)) == 0 ||
					(msg->sortorder=(char *)
				malloc(sm->nfields)) == 0)))
		write_error_exit(0);

	if (sm->nfields)
	{
		memset(msg->sortfields, 0, sizeof(char *)*sm->nfields);
		memset(msg->sortorder, 0, sm->nfields);
	}

	rev=0;
	i=0;

/* fprintf(stderr, "--\n"); */

	for (ss=sihead; ss; ss=ss->a)
	{
	char	*p;

		if (i >= sm->nfields)
			break;	/* Something's fucked up, better handle it
				** gracefully, instead of dumping core.
				*/
		switch (ss->type)	{
		case search_reverse:
			rev=1-rev;
			continue;
		case search_orderedsubj:
		case search_arrival:
		case search_cc:
		case search_date:
		case search_from:
		case search_size:
		case search_to:
			p=ss->as;
			if (!p)	p="";
			msg->sortfields[i]=my_strdup(p);
			msg->sortorder[i]=rev;
			/* fprintf(stderr, "%d %s\n", msg->sortorder[i], msg->sortfields[i]); */
			++i;
			rev=0;
			continue;
		default:
			break;
		}
		break;
	}

	msg->nfields=sm->nfields;
	msg->n=isuid ? current_maildir_info.msgs[n].uid:n+1;
	msg->next=sm->list;
	sm->list=msg;
	++sm->nmsgs;
}
