static char rcsid[] = "@(#)$Id: thread.c,v 1.6.2.2 2007/08/25 07:45:27 hurtta Exp $";

/******************************************************************************
 *  The Elm (ME+) Mail System  -  $Revision: 1.6.2.2 $   $State: Exp $
 *
 *  Author: Kari Hurtta <hurtta+elm@posti.FMI.FI>
 *      or  Kari Hurtta <elm@elmme-mailer.org>
 *****************************************************************************/

#include "def_messages.h"

DEBUG_VAR(Debug,__FILE__,"messages");

#if ANSI_C
#define S_(x) static x;
#else
#define S_(x)
#endif

static unsigned char *s2us P_((char *str));
static unsigned char *s2us(str) 
     char *str;
{
    return (unsigned char *)str;
}

#define THREAD_NAME_magic	0xF50A

static struct thread_name {
    unsigned short            magic;      /* THREAD_NAME_magic */

    struct string  * thread_title;

    struct thread_name  * smaller;     /* LEFT */
    struct thread_name  * bigger;      /* RIGHT */

    struct thread_name  * bad;         /* NO COMPARE */

    int            * thread_list;
    int              thread_list_len;

} * give_thread_name P_((struct thread_name **root, const struct string * thread_title));
static struct thread_name * give_thread_name(root, thread_title)
     struct thread_name **root; 
     const struct string * thread_title;
{
    struct thread_name *ptr = *root;
    int r;

    if (!ptr) {
	ptr = safe_malloc(sizeof(*ptr));

	bzero((void *)ptr, sizeof(*ptr));

	ptr->magic  = THREAD_NAME_magic;

	ptr->thread_title = dup_string(thread_title);
	ptr->smaller    = NULL;
	ptr->bigger     = NULL;	
	ptr->bad        = NULL;
	
	ptr->thread_list = NULL;
	ptr->thread_list_len = 0;
	
	*root = ptr;
	return ptr;
    }

    if (ptr->magic  != THREAD_NAME_magic)
	panic("MBX VIEW PANIC",__FILE__,__LINE__,"give_thread_name",
	      "Bad magic number",0);

    r = string_cmp(thread_title,ptr->thread_title,
		   999 /* == Values not comparable */);

    switch(r) {
	
    case 0:
	return ptr;
    case -1:

	return give_thread_name(& (ptr->bigger), thread_title);
    case 1:

	return give_thread_name(& (ptr->smaller), thread_title);
    case 999:
	return give_thread_name(& (ptr->bad), thread_title);
    }

    panic("MBX VIEW PANIC",__FILE__,__LINE__,"give_thread_name",
	  "Unexpected return from string_cmp()",0);    

    return NULL;
}

static void free_thread_names P_((struct thread_name **root));
static void free_thread_names(root)
     struct thread_name **root;
{
    struct thread_name *ptr = *root;

    if (ptr->magic  != THREAD_NAME_magic)
	panic("MBX VIEW PANIC",__FILE__,__LINE__,"free_thread_names",
	      "Bad magic number",0);

    if (ptr->smaller)
	free_thread_names(& (ptr->smaller));

    if (ptr->bigger)
	free_thread_names(& (ptr->bigger));

    if (ptr->bad)
	free_thread_names(& (ptr->bad));

    if (ptr->thread_title) {
	free_string(& (ptr->thread_title));
    }

    if (ptr->thread_list) {
	free(ptr->thread_list);
	ptr->thread_list = NULL;
    }
    ptr->thread_list_len = 0;

    ptr->magic = 0;  /* Invalidate */

    free(ptr);
    *root = NULL;
}

struct thread {
    struct thread_info *t;

    int  * msg_list;
    int    msg_list_len;
};

#define THREADVIEW_magic        0xF509

struct ThreadView {
    unsigned short            magic;    /* THREADVIEW_magic */

    struct thread_name  * name_root;

    struct thread       * thread;
    int                   thread_len;
};


static void free_thread_info P_((struct thread_info **t));
static void free_thread_info(t) 
     struct thread_info **t;
{
    if ((*t)->thread_subject)
	free_string(& ((*t)->thread_subject));

    free(*t);
    *t = NULL;
}

static struct thread_info *malloc_thread_info P_((void));
static struct thread_info *malloc_thread_info()
{
    struct thread_info * ret= safe_malloc(sizeof (*ret));

    bzero((void *)ret, sizeof(*ret));

    ret->thread_subject = NULL;

    return ret;
}


void free_thread_view(t)
     struct ThreadView **t;
{
    if (THREADVIEW_magic  != (*t)->magic)
	panic("MBX VIEW PANIC",__FILE__,__LINE__,"free_thread_view",
	      "Bad magic number",0);
        
    if ((*t)->thread) {
	int i;
	for (i = 0; i < (*t)->thread_len; i++) {
	    if ((*t)->thread[i].t)
		free_thread_info(& (*t)->thread[i].t);


	    if ((*t)->thread[i].msg_list) {		
		free((*t)->thread[i].msg_list);
		(*t)->thread[i].msg_list = NULL;
	    }
	    (*t)->thread[i].msg_list_len = 0;
	}
	
	free((*t)->thread);
	(*t)->thread = NULL;
    }
    (*t)->thread_len = 0;

    if ((*t)->name_root)
	free_thread_names(& ((*t)->name_root));

    (*t)->magic = 0;   /* Invalidate */
    free(*t);
    *t = NULL;
}

static struct ThreadView * malloc_mailbox_thread P_((void));
static struct ThreadView * malloc_mailbox_thread()
{
    struct ThreadView * ret;

    ret = safe_malloc(sizeof (*ret));

    /* bzero is defined hdrs/defs.h */
    bzero((void *)ret,sizeof (*ret));

    ret->magic      = THREADVIEW_magic;

    ret->name_root  = NULL;

    ret->thread     = NULL;
    ret->thread_len = 0;

    return ret;
}

static struct string *normalize_subject P_((const struct string *subject));
static struct string *normalize_subject(subject)
     const struct string *subject;
{
    struct string * temp   = skip_ascii_head_from_string(subject, s2us("Re: "),1);
    struct string * result = collapse_spaces_from_string(temp);

    free_string (&temp);

    return result;
}


struct thread_sort_data {
    struct MailboxView *mailbox;
    int index;
};

static int compare_thread P_((struct thread_sort_data *X1,
			      struct thread_sort_data *X2));
static int compare_thread(X1,X2) 
     struct thread_sort_data *X1;
     struct thread_sort_data *X2;
{
    struct header_rec *hdr1 = 
	X1->mailbox->mailbox_type->
	mt_give_header_it(X1->mailbox,X1->index,
			  & X1->mailbox->view[X1->index]);
    struct header_rec *hdr2 = 
	X2->mailbox->mailbox_type->
	mt_give_header_it(X2->mailbox,X2->index,
			  & X2->mailbox->view[X2->index]);
    
    struct string * from1, * from2;

    int ret;

    if (!hdr1 || !hdr2)
	return 0;

    if (!hdr1->subject && hdr2->subject) 
	return -1;    
    if (hdr1->subject && !hdr2->subject) 
	return 1;
    if (!hdr1->subject && !hdr2->subject) 
	return 0;

    from1 =  normalize_subject(hdr1->subject);
    from2 =  normalize_subject(hdr2->subject);

    ret = string_cmp(from1,from2, 
		     0 /* == Values not comparable */ );

    free_string(&from1);
    free_string(&from2);

    if (0 == ret) {
	long diff;
	
	diff = hdr1->time_sent - hdr2->time_sent;
	if ( diff < 0 )	ret = -1;
	else if ( diff > 0 ) ret = 1;
	else ret = 0;
    }
    
    return ret;
}

void update_mailbox_threads(mailbox)
     struct MailboxView *mailbox;
{
    int i;
    int not_added = 0;
    struct ThreadView * TV;

    if (mailbox->magic         != MAILBOXVIEW_magic)
	panic("MBX VIEW PANIC",__FILE__,__LINE__,"update_mailbox_threads",
	      "Bad magic number",0);

    if (! mailbox->thread_view)
	mailbox->thread_view = malloc_mailbox_thread();

    if (mailbox->thread_view->magic != THREADVIEW_magic)
	panic("MBX VIEW PANIC",__FILE__,__LINE__,"update_mailbox_threads",
	      "Bad thread view magic number",0);

    TV = mailbox->thread_view;

    for (i = 0; i < TV->thread_len; i++) {
	/* Just reset counters -- msg_list_len is realloced later */

	TV->thread[i].msg_list_len = 0;
    }

    if (mailbox->mailbox_type->magic != MAILBOXTYPE_magic)
	panic("MBX VIEW PANIC",__FILE__,__LINE__,"update_mailbox_threads",
	      "Bad type magic number",0);

    /* Read numbers */

    for (i = 0; i < mailbox->view_len; i++) {

	if (-1 == mailbox->view[i].thread_number)
	    not_added++;
	else {
	    int index = mailbox->view[i].thread_number;
	    struct string * subj1 = NULL;

	    if (index < 0 || index >= TV->thread_len) {
		DPRINT(Debug,1,(&Debug,
				"update_mailbox_threads: [%d] bad thread index %d\n",
				i,index));
		mailbox->view[i].thread_number = -1;
		not_added++;
	    } else {
		struct header_rec *hdr = mailbox->mailbox_type->
		    mt_give_header_it(mailbox,i,
				      & mailbox->view[i]);

		struct thread_info * T = TV->thread[index].t;

		if (!hdr) {
		    DPRINT(Debug,7,(&Debug,
				    "update_mailbox_threads: [%d] thread index %d -- no header available\n",
				    i,index));
		    mailbox->view[i].thread_number = -1;
		    not_added++;
		    continue;
		}

		if (!hdr->subject) {
		    DPRINT(Debug,7,(&Debug,
				    "update_mailbox_threads: [%d] thread index %d -- no subject\n",
				    i,index));

		    subj1 = new_string(system_charset);
		} else {
		    subj1 = normalize_subject(hdr->subject);
		}

		if (0 != string_cmp(T->thread_subject,subj1,
				    999 /* == Values not comparable */)) {

		    DPRINT(Debug,7,(&Debug,
				    "update_mailbox_threads: [%d] thread index %d -- subject not match\n",
				    i,index));
		    mailbox->view[i].thread_number = -1;
		    not_added++;

		    DPRINT(Debug,7,(&Debug,"  thread %d=%S\n",
				    index,T->thread_subject));
		    DPRINT(Debug,7,(&Debug,"  mail %d=%S\n",
				    i,subj1));

		    free_string(&subj1);
		    continue;		    
		}

		if (hdr->time_sent < 
		    T->time_sent_first - 
		    (long) sort_thread_max_time * 24 * 60 * 60 ||
		    hdr->time_sent >
		    T->time_sent_last +
		    (long) sort_thread_max_time * 24 * 60 * 60) {

		    DPRINT(Debug,7,(&Debug,
				    "update_mailbox_threads: [%d] thread index %d -- time not match\n",
				    i,index));
		    mailbox->view[i].thread_number = -1;
		    not_added++;


		    DPRINT(Debug,7,(&Debug,"  thread %d= first time=%ld last time=%ld\n",
				    index,
				    (long) T->time_sent_first,
				    (long) T->time_sent_last));
		    DPRINT(Debug,7,(&Debug,"  mail %d= time sent=%ld\n",
				    i,
				    hdr->time_sent));
		    
		    free_string(&subj1);
		    continue;		    
		}
		    
		TV->thread[index].msg_list =
		    safe_realloc(TV->thread[index].msg_list,
				 (TV->thread[index].msg_list_len+1) *
				 sizeof (TV->thread[index].msg_list[0]));

		TV->thread[index].msg_list[TV->thread[index].msg_list_len]
		    = i;
		TV->thread[index].msg_list_len++;

		free_string(&subj1);
	    }
	}
    }

    for (i = 0; i < TV->thread_len; i++) {
	TV->thread[i].t->num_messages =
	    TV->thread[i].msg_list_len;
    }

    DPRINT(Debug,7,(&Debug,
		    "update_mailbox_threads: not_added=%d\n",not_added));

    if (not_added > 0) {
	struct thread_sort_data  * sort_data = 
	    safe_malloc(not_added * sizeof(sort_data[0]));
	int idx = 0;

	int outer, inner = 0;

	typedef int (*compar) P_((const void *, const void *));
	compar X = (compar) compare_thread;


	for (i = 0; i < mailbox->view_len; i++) {
	    
	    if (-1 == mailbox->view[i].thread_number) {

		if (idx >= not_added)
		    panic("MBX VIEW PANIC",__FILE__,__LINE__,"update_mailbox_threads",
			  "Overflow",0);
		
		sort_data[idx].mailbox = mailbox;
		sort_data[idx].index   = i;
		idx++;
	    }
	}

	/* Sort with subject and sent_time */

	qsort(sort_data,idx,sizeof (sort_data[0]), X);

	for (outer = 0; outer < idx; outer = inner) {
	    int index = sort_data[outer].index;
	    struct header_rec *hdr;
	    struct string * subj1 = NULL;
	    time_t thread_first;
	    time_t thread_last;
	    int thread_idx;
	    int scan_idx;
	    int count;
	    int j;
	    struct thread_name   * thread_name = NULL;


	    if (index < 0 || index >= mailbox->view_len)
		panic("MBX VIEW PANIC",__FILE__,__LINE__,"update_mailbox_threads",
		      "Bad index",0);
		
	    hdr = mailbox->mailbox_type->
		mt_give_header_it(mailbox,index,
				  & mailbox->view[index]);
	    
	    if (!hdr) {
		DPRINT(Debug,7,(&Debug,
				"update_mailbox_threads: [%d] -- no header available\n",
				index));
		inner = outer+1;
		continue;
	    }

	    if (!hdr->subject) {
		DPRINT(Debug,7,(&Debug,
				"update_mailbox_threads: [%d] -- no subject\n",
				index));
		
		subj1 = new_string(system_charset);
	    } else {
		subj1 = normalize_subject(hdr->subject);
	    }
	    
	    thread_first = hdr->time_sent ;
	    thread_last = hdr->time_sent ;

	    for (inner = outer+1; inner < idx; inner++) {
		struct header_rec *hdr2;
		int index2 = sort_data[inner].index;
		struct string * subj2 = NULL;

		if (index2 < 0 || index2 >= mailbox->view_len)
		    panic("MBX VIEW PANIC",__FILE__,__LINE__,"update_mailbox_threads",
			  "Bad index",0);

		hdr2 = mailbox->mailbox_type->
		    mt_give_header_it(mailbox,index2,
				      & mailbox->view[index2]);

		if (!hdr2) {
		    DPRINT(Debug,7,(&Debug,
				    "update_mailbox_threads: [%d] -- no header available\n",
				    index2));
		    break;
		}

		if (!hdr2->subject) {
		    DPRINT(Debug,7,(&Debug,
				    "update_mailbox_threads: [%d] -- no subject\n",
				    index2));

		    subj2 = new_string(system_charset);
		} else {
		    subj2 = normalize_subject(hdr2->subject);
		}
		
		if (0 != string_cmp(subj2,subj1,
				    999 /* == Values not comparable */)) {
		    
		    free_string(& subj2);    /* NO MATCH */
		    break;
		}

		if (hdr2->time_sent < 
		    thread_first - 
		    (long) sort_thread_max_time * 24 * 60 * 60 ||
		    hdr2->time_sent >
		    thread_last +
		    (long) sort_thread_max_time * 24 * 60 * 60) {

		    free_string(& subj2);    /* NO MATCH */
		    break;
		}

		/* OK */

		if (hdr2->time_sent < thread_first)
		    thread_first = hdr2->time_sent;
		if (hdr2->time_sent > thread_last)
		    thread_last = hdr2->time_sent;
		
		free_string(& subj2);  
	    }
	    

	    thread_name = give_thread_name(& (TV->name_root), subj1);

	    
	    for (scan_idx = 0; scan_idx < thread_name->thread_list_len; scan_idx++) {
		
		struct thread_info * T;

		thread_idx = thread_name->thread_list[scan_idx];
		
		if (thread_idx < 0 || thread_idx >= TV->thread_len)
		    panic("MBX VIEW PANIC",__FILE__,__LINE__,"update_mailbox_threads",
			  "Bad thread index",0);
		    
		T = TV->thread[thread_idx].t;

		if (0 != string_cmp(T->thread_subject,subj1,
				    999 /* == Values not comparable */))
		    panic("MBX VIEW PANIC",__FILE__,__LINE__,"update_mailbox_threads",
			  "Bad thread index",0);
		
		if (thread_last > T->time_sent_first - 
		    (long) sort_thread_max_time * 24 * 60 * 60 &&
		    thread_first < T->time_sent_last +
		    (long) sort_thread_max_time * 24 * 60 * 60) {

		    if (thread_first < T->time_sent_first)
			T->time_sent_first = thread_first;
		    if (thread_last > T->time_sent_last)
			T->time_sent_last = thread_last;

		    
		    goto found_thread;
		}
	    }


	    TV->thread = safe_realloc(TV->thread, 
				      ( TV->thread_len+1) * 
				      sizeof (TV->thread[0]));
	    
	    thread_idx = TV->thread_len;
	    
	    bzero((void *)& (TV->thread[thread_idx]), 
		  sizeof(TV->thread[thread_idx]));

	    TV->thread_len++;

	    TV->thread[thread_idx].t = malloc_thread_info();
	    TV->thread[thread_idx].t->time_sent_first = thread_first;
	    TV->thread[thread_idx].t->time_sent_last  = thread_last;
	    TV->thread[thread_idx].t->thread_subject  = dup_string(subj1);

	    TV->thread[thread_idx].msg_list     = NULL;
	    TV->thread[thread_idx].msg_list_len = 0;


	    DPRINT(Debug,7,(&Debug,
			    "update_mailbox_threads: Adding thread #%d to thread subject: %S\n",
			    thread_idx,thread_name->thread_title));

	    thread_name->thread_list = 
		safe_realloc(thread_name->thread_list,
			     (thread_name->thread_list_len +1) *
			     sizeof(thread_name->thread_list[0]));
	    thread_name->thread_list[thread_name->thread_list_len] = thread_idx;
	    thread_name->thread_list_len++;

	found_thread:

	    count = inner-outer;

	    DPRINT(Debug,7,(&Debug,
			    "update_mailbox_threads: Adding %d messages to thread %d: %S\n",
			    count,thread_idx,
			    TV->thread[thread_idx].t->thread_subject));
	    DPRINT(Debug,7,(&Debug,
			    "    messages sent time %ld - %ld\n",
			    thread_first,
			    thread_last));
	    DPRINT(Debug,7,(&Debug,
			    "    thread sent time %ld - %ld\n",
			    TV->thread[thread_idx].t->time_sent_first,
			    TV->thread[thread_idx].t->time_sent_last));


	    if (!count) 
		panic("MBX VIEW PANIC",__FILE__,__LINE__,"update_mailbox_threads",
		      "Empty count",0);
	    
	    count += TV->thread[thread_idx].msg_list_len;

	    TV->thread[thread_idx].msg_list  = 
		safe_realloc(TV->thread[thread_idx].msg_list,
			     count * sizeof(TV->thread[thread_idx].msg_list[0]));

	    for (j = outer; j < inner; j++) {
		int index = sort_data[j].index;
		if (TV->thread[thread_idx].msg_list_len >= count)
		    panic("MBX VIEW PANIC",__FILE__,__LINE__,"update_mailbox_threads",
			  "Overflow",0);
		    
		TV->thread[thread_idx].msg_list[TV->thread[thread_idx].msg_list_len]
		    = index;
		TV->thread[thread_idx].msg_list_len++;

		if (index < 0 || index >= mailbox->view_len)
		    panic("MBX VIEW PANIC",__FILE__,__LINE__,"update_mailbox_threads",
			  "Bad index",0);
		mailbox->view[index].thread_number = thread_idx;		    
	    }


	    TV->thread[thread_idx].t->num_messages =
		TV->thread[thread_idx].msg_list_len;
    
	    free_string(&subj1);
	}

	free(sort_data);
    }

}

CONST struct thread_info * give_thread_info_s(s)
     struct sort_data *s;
{
    int idx;

    if (s->sort_data_type->magic != SORTDATATYPE_magic)
	panic("MBX VIEW PANIC",__FILE__,__LINE__,"give_thread_info_s",
	      "Bad magic number",0);
    
    if (THREADVIEW_magic != s->t->magic)
	panic("MBX VIEW PANIC",__FILE__,__LINE__,"give_thread_info_s",
	      "Bad threadview magic number",0);

    idx = s->w.thread_number;
    if (-1 == idx)
	return NULL;     /* No thread information */

    if (idx < 0 || idx >= s->t->thread_len)
	panic("MBX VIEW PANIC",__FILE__,__LINE__,"give_thread_info_s",
	      "Bad thread index",0);

    return s->t->thread[idx].t;
	
}

int get_thread_count(mailbox, create)
     struct MailboxView *mailbox;
     int create;
{
    if (mailbox->magic         != MAILBOXVIEW_magic)
	panic("MBX VIEW PANIC",__FILE__,__LINE__,"get_thread_count",
	      "Bad magic number",0);

    if (! mailbox->thread_view) {
	if (!create)
	    return -1;

	update_mailbox_threads(mailbox);
    }

    if (mailbox->thread_view->magic != THREADVIEW_magic)
	panic("MBX VIEW PANIC",__FILE__,__LINE__,"get_thread_count",
	      "Bad thread view magic number",0);
    
    return mailbox->thread_view->thread_len;
}

CONST struct thread_info * give_thread_info(mailbox,idx)
     struct MailboxView * mailbox; 
     int idx;
{
    if (mailbox->magic         != MAILBOXVIEW_magic)
	panic("MBX VIEW PANIC",__FILE__,__LINE__,"give_thread_info",
	      "Bad magic number",0);

    if (! mailbox->thread_view)
	return NULL;

    if (mailbox->thread_view->magic != THREADVIEW_magic)
	panic("MBX VIEW PANIC",__FILE__,__LINE__,"give_thread_info",
	      "Bad thread view magic number",0);

    if (idx < 0 || idx >= mailbox->thread_view->thread_len)
	return NULL;

    return mailbox->thread_view->thread[idx].t;
}

/* caller must free result */
int * give_thread_message_list (mailbox,idx,reslen)
     struct MailboxView * mailbox; 
     int idx;
     int *reslen;
{
    int * res = NULL;
    int i;

    if (mailbox->magic         != MAILBOXVIEW_magic)
	panic("MBX VIEW PANIC",__FILE__,__LINE__,"give_thread_message_list",
	      "Bad magic number",0);
    
    *reslen = 0;

    if (! mailbox->thread_view)
	return NULL;

    if (mailbox->thread_view->magic != THREADVIEW_magic)
	panic("MBX VIEW PANIC",__FILE__,__LINE__,"give_thread_info",
	      "Bad thread view magic number",0);

    if (idx < 0 || idx >= mailbox->thread_view->thread_len)
	return NULL;
    
    if (mailbox->thread_view->thread[idx].msg_list_len < 1)
	return NULL;

    *reslen = mailbox->thread_view->thread[idx].msg_list_len;
    res = safe_malloc((*reslen) * sizeof(res[0]));

    for (i = 0; i < *reslen; i++)
	res[i] = mailbox->thread_view->thread[idx].msg_list[i];

    return res;
}

/*
 * Local Variables:
 *  mode:c
 *  c-basic-offset:4
 *  buffer-file-coding-system: iso-8859-1
 * End:
 */


syntax highlighted by Code2HTML, v. 0.9.1