// ------------------------------------------------------------------------------------------------
// DESCRIPTION  Mail message MIME structure for display processing purposes
// ------------------------------------------------------------------------------------------------
// NOTES build up from local message (surgeweb) or imaplib use of IMAP BODYSTRUCTURE command
// ------------------------------------------------------------------------------------------------

#include <errno.h>
#include <string.h>
#include <stdio.h>

#define DMID 14
#include "lib.h"
#include "parts.h"
#include "debug.h"
#include "thread.h"
#include "rstring.h"
#include "mime.h"
#include "prcc.h"
#include "lprintf.h"
int emsg(const char * arg_list, ...);
void parts_init(PartAllocator *pa, SwPart **parts)
{
	memset(pa,0,sizeof(PartAllocator));
	pa->last_next=parts;
}

// allocate SwPart and maintain pointers in parts list
SwPart* parts_new_part_real(PartAllocator* pa, int depth, char* dbg_filename, char* file, int line)
{
	SwPart* p;
	char xreason[BFSZ];

	if (depth >= MAX_MIME_DEPTH){
		if(dbg_filename!=NULL){
			lib_copy_file(dbg_filename,"bad_mime.tmp",xreason);
			emsg("parts_new_part_real BAILOUT depth=%d file=%s (see bad_mime.tmp)", depth, dbg_filename);
		}else{
			emsg("parts_new_part_real BAILOUT depth=%d file=NULL", depth);
		}
		return NULL;
	}

	p = d_callocid(DMID, 1, sizeof(SwPart), file, line);
	*pa->last_next = p;
	pa->last_next = &p->next;

	// setup additional pointers for tree based navigation
	if (depth == 1)
		pa->top = p;

	// Make sure we know about first child
	if (pa->lastp && depth > pa->last_depth && pa->lastp->children == NULL)
		pa->lastp->children = p;

	// Make sure we know about last siblings
	if (depth < 0 || depth >= MAX_MIME_DEPTH) { 
		if(dbg_filename!=NULL){
			emsg("parts_new_part_real depth=%d file=%s (see bad_mime.tmp)", depth, dbg_filename);
			lib_copy_file(dbg_filename,"bad_mime.tmp",xreason);
		}else{
			emsg("parts_new_part_real depth=%d file=NULL", depth);
		}
		lib_abort(); abort(); 
	}
	if (depth<=pa->last_depth && pa->last_siblings[depth]!=NULL)
		*pa->last_siblings[depth]=p;
	pa->last_siblings[depth]=&p->siblings;
	pa->ancestors[depth]=p;

    // And navigation pointers back up the tree while we are at it
    if(depth>1){
        p->parent=pa->ancestors[depth-1];
    }else{
        p->parent=NULL;
    }
    
    pa->last_depth=depth;
	pa->lastp=p;

	return p;
}
SwPart *parts_display_part_basic(SwPart *parts)
{
	SwPart *part;
	for (part=parts;part;part=part->next){
		if (part->type && strcmpnc(part->type,"text")==0) {
			return part;
		}
	}
	return parts;
}
void parts_display_part_basichtml(SwPart *parts, SwPart **text_part, SwPart **html_part)
{
	SwPart *part, *candidates[10];
    int i=0, nparts=0;
	char *part_subtype=NULL;
    *text_part = *html_part = NULL;
    for (part=parts;part;part=part->next){
        if(i>5)break;
        if (part->is_attachment) continue;
		if (part->type && strcmpnc(part->type,"text")==0) {
			candidates[i]=part;
            i++;
		}
	}
    candidates[i]=0;
	if (i==0) {
        *text_part = parts;
        return;
    }
    nparts = i;

    // Should be fixed to handle attachments multiple inline parts etc, this will do for now though...
    for (i=0; i<nparts; i++) {
        part = candidates[i];
		part_subtype = nullok(part->subtype);
        if(*text_part==NULL && (!part->subtype || strcmpnc(part_subtype,"plain")==0) && (strcmpnc(part->subtype,"html")!=0))
            *text_part = part;
        if(*html_part==NULL && (strcmpnc(part_subtype,"html")==0))
            *html_part = part;
    }
    
}

//	prt = sw_find_display_parts(sw, prts, FALSE);				// cf mw_find_display_part_cimap

// SwPart *sw_find_display_parts(Sw *sw, SwPart *prts, int nested);
// SwPart *sw_recurse_display_parts(Sw *sw, SwPart *prt, PartRecurse *d, int ancestor_related, int parent_mixed);




void parts_free(SwPart **parts)
{
	SwPart *p,*pnext;
	if(!*parts) return;
	for(p=*parts;p;p=pnext){
		pnext=p->next;

		ffree(p->name);
		ffree(p->filename);
		ffree(p->type);
		ffree(p->subtype);
		ffree(p->encoding);
		ffree(p->cid);
		ffree(p->cloc);

		pair_free(&p->hdrs);

		partslist_free(&p->images);
		partslist_free(&p->attachments);
		partslist_free(&p->attachments_mpr);

		ffree(p);
	}
	*parts=NULL;
}

// List to keep track of SWpart pointers (eg parts to display as attachments etc)
int partslist_add_real(PartsList**list, SwPart *ptr, int atend, char *file, int line)
{
	PartsList *p,*end=NULL;

	for (p=*list;p!=NULL;p=p->next)
		end = p;

	p = d_callocid(DMID,1,sizeof(PartsList),file,line);
	p->ptr = ptr;

	if (atend && end) {
		p->next = NULL;
		end->next = p;
	} else {
		p->next = *list;
		*list = p;
	}
	return TRUE;
}
int partslist_free(PartsList**list)
{
	PartsList *p,*pnext;
	if (list==NULL) return TRUE;
	for (p=*list;p!=NULL;p=pnext) {
		pnext = p->next;
		free(p);
	}
	*list = NULL;
	return TRUE;
}



////
#ifdef _migrating_

SwPart *sw_find_display_parts(Sw *sw, SwPart *top, int nested)  // cf: mw_find_display_part_cimap < mw_recurse_display_parts_cimap < init_part_recurse_cimap
{
	SwPart *wanted=NULL;
	PartRecurse d;

	char *send_mode,tmp[BFSZ];

	init_part_recurse(&d);

	imsg("find display parts - work out text/html part + attachment parts to display");

	if (strcmpnc(top->type,"text")==0){
		wanted = top;
		if(top->subtype!=NULL){
			if(strcmpnc(top->subtype,"plain")==0) d.text1=top;
			if(strcmpnc(top->subtype,"html")==0)  d.html1=top;
		}

	} else if (strcmpnc(top->type,"multipart")==0){

		if(strcmpnc(top->subtype,"voice-message")==0){	// treat as standard mixed
			ffree(top->subtype);
			top->subtype=strdup("mixed");
		}

		// Different mail clients nest complex MIME messages differently!!
		// Need to handle a variety of nesting orders of "mixed", "alternative", and "related"
		if (strcmpnc(top->subtype,"mixed")==0 || strcmpnc(top->subtype,"report")==0
				|| strcmpnc(top->subtype,"alternative")==0 || strcmpnc(top->subtype,"related")==0 || strcmpnc(top->subtype,"signed")==0 ){

			// Hunt for first text/html part
			wanted = sw_recurse_display_parts(sw,top,&d,FALSE,FALSE);

			// Have not found the part we wanted so try use first html, or first text part if none
			// Do we try any html <-> text translation if we did not find a wanted part, dunno maybe in the future??
			if (!wanted)
				wanted = d.html1 ? d.html1 : d.text1;

			top->images=d.images;
			top->attachments=d.attachments;
			top->attachments_mpr=d.attachments_mpr;
			if(!nested){
				output_attach_counts(sw,top,wanted);
			}
		}else if (strcmpnc(top->subtype,"digest")==0){

			//do something special (display all / display none??)
		}
	}
	// Still not found a wanted part :-(( try to display the whole message as text but only if sensible to do so
	if (!wanted){
		if(top->filename && lib_strlen(top->filename)>0){
			// This is a message containing just a single attachment part and no text parts!!
			partslist_add_atend(&top->attachments,top);
		}else if(strcmpnc(top->type,"multipart")==0 && strcmpnc(top->subtype,"mixed")==0){
			// Sorry no sensible top part
		}else{
			if(sw->fld){
				char path[BFSZ];
				sw_cred_path(sw,path);
				wmsg_body_fname_raw(path, sw->fld->disk.fid, sw->mid, tmp);
			}else{
				ncpy(tmp,"",BFSZ-1);
			}
			emsg("surgeweb: defaulting to whole message (%s|%s)",sw->ses->email, tmp);
			wanted = top;
		}
	}

	if (d.text1) web_var_add( sw->w, "text_part", d.text1->name);
	if (d.html1) web_var_add( sw->w, "html_part", d.html1->name);

	// Work out whether we prefer to reply to this message in plain text or html but it all gets a bit tricky, ideally:
	// - if message is html, we reply in html
	// - if we received the message as plain text from a smart mail client supporting format=flowed (thunderbird / osx mail, etc) 
	//   use plain text format flowed
	// - if we received message as plain text from microsoft mail client, then use html as it gets messed up with format flowed text
	// Note: not sure if we need any additional setting based, or recipient based selection. The user can always override what we decide here.
	send_mode="html";
	if(d.text1 && wanted==d.text1 && ( istrue(pair_find(d.text1->hdrs,"flowed")) || pair_find_null(top->hdrs,"X-DBabble")!=0)){
		send_mode="plain_text";
	}
//	web_var_add(sw->w,"send_mode",(d.html1?"html":"plain_text"));	
	web_var_add(sw->w,"send_mode",send_mode);	

	if(wanted)
		imsg("decided on part=%s %s/%s (fld_id=%s mid=%d)\n",wanted->name,wanted->type,wanted->subtype,sw->fld_id,sw->mid);
	else
		imsg("no text part to display");
	return wanted;
}

typedef struct PARTRECURSE {SwPart *text1, *html1, *wanted; int want_text, want_html; PartsList *images,*attachments,*attachments_mpr;} PartRecurse;

PartRecurse *init_part_recurse(PartRecurse *d){
	memset(d,0,sizeof(PartRecurse));
	d->want_html=1;
	return d;
}

SwPart *sw_recurse_display_parts(Sw *sw, SwPart *prt, PartRecurse *d, int ancestor_related, int parent_mixed)
{
	SwPart *p,*p2,*linked_last=NULL;
	int isplain, ishtml, try_display, hide_attachment, n, text_with_inline=FALSE;
	char *cid, *disp;

	// walk all parts at this level
	for( p=prt; p; p=p->siblings){

		if (p->type==NULL) continue;
		hide_attachment=FALSE;

		// Handle nested "mixed", "alternative", and "related"
		if (strcmpnc(p->type,"multipart")==0){
			if (p->subtype==NULL) continue;
			if (strcmpnc(p->subtype,"related")==0 )
				sw_recurse_display_parts(sw,p->children,d,TRUE,FALSE);
			if (strcmpnc(p->subtype,"mixed")==0 || strcmpnc(p->subtype,"report")==0 || strcmpnc(p->subtype,"appledouble")==0)
				sw_recurse_display_parts(sw,p->children,d,ancestor_related,TRUE);
			if (strcmpnc(p->subtype,"alternative")==0 || strcmpnc(p->subtype,"signed")==0)
				sw_recurse_display_parts(sw,p->children,d,ancestor_related,FALSE);
			continue;
		}

		if (strcmpnc(p->type,"application")==0 && p->subtype && strcmpnc(p->subtype,"applefile")==0){
			// data fork of "multipart/appledouble" ignore
			continue;
		}

		isplain = sw_ispart(p,"text","plain");
		ishtml = sw_ispart(p,"text","html")&&!p->filename;		/*!parent_mixed is not a valid test as you get multipart mixed messages with just an html mime part!!*/

		if(d->wanted /*ie "have part for display"*/){ 
			// Message containing other messages for display
			if (sw_ispart_message(p)){
				if(istrue(web_var_find(sw->w,"show_attach_inline"))){
					for(p2=d->wanted;p2->linked_next;p2=p2->linked_next);
					p2->linked_next = p;
				}
				n=atoi(web_var_find(sw->w,"attachments_email"));
				web_var_addi(sw->w,"attachments_email",n+1);
			}
		}
		try_display=TRUE;
		disp=pair_find(p->hdrs,"Content-Disposition");
		if(parent_mixed){
			// Multiple consecutive text parts as one message (As generated by Apple products)
			if(isplain && (!linked_last || text_with_inline) ){
				if(linked_last){
					linked_last->linked_next = p;
				}
				linked_last=p;
			}
			// Have images as above displayed inline
			if(linked_last && (p->type && strcmpnc(p->type,"image")==0) && strstrnc(disp,"inline") && !istrue(web_var_find(sw->w,"pref_no_interleaved"))){
				linked_last->linked_next = p;
				linked_last=p;
				hide_attachment=TRUE;		// QQQS
				text_with_inline=TRUE;
				web_var_add(sw->w,"related_content","true");
			}
			// Can have multipart mixed messages with no message body but with attachments
			if(strstrnc(disp,"attachment")){
				try_display=FALSE;
			}
		}

		if (try_display){
			// Keep track of first plain / html parts we find if we need to fallback to them later
			if (isplain && !d->text1) d->text1=p;
			if (ishtml && !d->html1) d->html1=p;

			// Try and select the part for preferred display method
			if (isplain && d->wanted==NULL/*&& d->want_text*/ || ishtml && d->want_html && p==d->html1){
				d->wanted = p;
				continue;
			}
		}

		// Add required remaining parts to be displayed as attachments
		cid=pair_findnc(p->hdrs,"Content-ID");
		// Test for parts to be displayed as attachments (used to be "!ancestor_related" but that is too limited)
		//  ?? are there ever any parts that are part of multipart related that do not have a cid: tag?
		// Return to !ancestor_related as I cannot remember reason for original change and have examples of 
		//	message with a CID that are partof multipart/mixed
//		if (!(/*ancestor_related ||*/ hide_attachment)){	/*|| lib_strlen(cid)==0*/	// QQQS
//			if (strcmpnc(p->type,"image")==0 && !(ancestor_related||linked_last)){
		if (!(ancestor_related || hide_attachment) || (strcmp(web_var_find(sw->w,"mpr_as"),"attach")==0)){	/*|| lib_strlen(cid)==0*/	// QQQS
			if (strcmpnc(p->type,"image")==0 ){
				partslist_add_atend(&d->images,p);
			}
			//add all parts as a standard attachment;
			if (TRUE/*parent_mixed*/){	// Not sure anymore why this needed to be removed
				if (p==d->text1) continue;
				if (p==d->html1) continue;
				// This won't do as some images are listed as inline
//				s=pair_find(p->hdrs,"Content-Disposition");
//				if (s && strstr(s,"inline")) continue;

				if (!p->filename){
					// Make sure we do not ignore message attachments
					if(sw_ispart(p,"message","rfc822")){
						p->filename=strdup(RFC_MESSAGE_NAME);
					}
					// Deal with feedback-reports
					if (sw_ispart(p,"message","feedback-report")){
						p->filename=strdup(FEEDBACK_REPORT);
					}
				}

				if (!p->filename || lib_strlen(p->filename)==0) continue;
				partslist_add_atend(&d->attachments,p);
			}
		}
		if(ancestor_related /*lib_strlen(cid)>0*/ /*ie. is "ancestor_related"*/ ){
			// hack to provide warning
			web_var_add(sw->w,"related_content","true");
			// QQMPR
			partslist_add_atend(&d->attachments_mpr,p);
		}
	}
	return d->wanted;
}

#endif

SwPart *parts_findpart_bycid(SwPart *parts, char *cid)
{
	SwPart *p;
	for (p=parts;p;p=p->next){
		if (strstr(p->cid,cid)!=0)
			return p;
	}
	return NULL;
}
SwPart *parts_findpart_bycloc(SwPart *parts, char *cloc)
{
	SwPart *p;
	for (p=parts;p;p=p->next){
		if (strcmp(p->cloc,cloc)==0)
			return p;
	}
	return NULL;
}
SwPart *parts_findpart_byname(SwPart *parts, char *name)
{
	SwPart *p;
	for (p=parts;p;p=p->next){
		if (p->filename && strcmp(p->filename,name)==0)
			return p;
	}
	return NULL;
}

// mime decode message structure to work out 'in memory' parts structure list/tree
int parts_decode_file_real(SwPart **parts, FILE *f, char* dbg_filename, char *file, int line)			// cf sw_decode_parts_cimap, wparts_load+wm_parse_part+msg_parts_cb / cimap_read_msg
{
	Mime *m = NULL;
	SwPart *p, *q;
	char raw[BFSZ],bin[BFSZ],*s,hdr[BFSZ],*tmp,bf[BFSZ];
	int k,len,exists,n,n2;
	PartAllocator pa;
	int size=0;
	int loop=0;

//	imsg("decode message for mime structure");
    if(*parts!=NULL){
//        emsg("Probable leak found");
        lib_abort();
    }

	parts_init(&pa,parts);
	p=parts_new_part_real(&pa, 0, dbg_filename, file, line);
	p->name=strdup("0");	// QQQBUG	0

	fseek(f,0,SEEK_SET);
	mime_new(&m);
	m->append_lines = TRUE;
	while(k = mime_line(m,f,raw,bin,&len,BFSZ-1)) {
//		if(loop==180){
//			_asm{int 03}		// Debug odd mime issues
//		}
		loop++;
		switch(k) {
		case MIME_BOUNDARY:

			//if (m->level>5) handle it nicely		// Sanity check for mime bombs etc
			// this does not work
//			if (m->partlvl < m->level) break;		// ignore closing mime boundaries
			for (q=*parts,exists=FALSE;q;q=q->next){			//   I really do not like this check though
				if (strcmp(q->name,m->part)==0){
					exists=TRUE;
					break;
				}
			}
			if (exists){
				p=NULL; break;
			}
			p=parts_new_part(&pa, m->partlvl, dbg_filename);				//cf: p = wpart_new(w,top->msg,m->part,f);
			if(!p) continue;
			p->name=strdup(m->part);
//            p->parent=xx;
            break;
		case MIME_CONTENT:
		case MIME_HEADER:
//			if (k == MIME_CONTENT && strstrnc(m->content,"text/html")) p->ishtml = TRUE;
			if (!p) break;							// Get this on closing mime boundaries - do not expect any headers there though!!
			if (m->fname && m->fname[0]){
				int change_it = p->filename==NULL;
				if(!change_it){
					change_it = strcmp(m->fname,p->filename) != 0;
				}
				if(change_it){
					mime_header_decode(m->fname,bf,BFSZ-1);			
//					ncpy(bf,m->fname,BFSZ-1); // TEST...
					mime_clean_line_breaks(bf);
					if(p->filename) ffree(p->filename); // Make Content-Disposition:filename override Content-Type:name
					p->filename = strdup(bf);
				}
			}
			// Turn headers across multiple lines to a single line
			mime_clean_line_breaks(raw);
			s = strchr(raw,':');
			if (s && !lib_space_before(raw,s)){
				for(*s++ = 0; sisspace(*s); s++) {}
				ncpy(hdr,raw,BFSZ-1);
				pair_add(&p->hdrs,hdr,strtrm(s));
			}else{
				if(!p->hdrs||!p->hdrs->var) break;
				// Allow full length to and cc fields
				if(!(strcmpnc(p->hdrs->var,"to")==0 || strcmpnc(p->hdrs->var,"cc")==0 || strcmpnc(p->hdrs->var,"bcc")==0)) break;
				n=lib_strlen(p->hdrs->val);
				n2=n+lib_strlen(raw)+1;
//				imsg("adding to previous header [%s] %d -> %d  [%.20s...%.20s][%.20s...%.20s]",p->hdrs->var,n,n2,
//									p->hdrs->val,p->hdrs->val+n-20, raw,raw+lib_strlen(raw)-20);
				tmp=calloc(1,n2+1);
				lprintf(tmp,n2,"%s%s",p->hdrs->val,raw);
				pair_add(&p->hdrs,p->hdrs->var,tmp);
				free(tmp);
			}
			break;
		default:
			if (p) p->size+=len;	// Work out size of this part
			break;
		}
	}
    if(p && !p->children){
        ffree(p->name);
        p->name=strdup(m->part);
    }
	mime_free(&m);

	// Do additional decoding now for better speed later	// QQQParts
	for(p=*parts,n=0;p;p=p->next,n++){
		s=pair_findnc(p->hdrs,"Content-Type");
		prcc_decode_content(p,s);
		if(strstrnc(s,"format=flowed")){
			pair_add(&p->hdrs,"flowed","true");
		}
		s=pair_findnc(p->hdrs,"Content-Transfer-Encoding");
		if (s[0]!='\0'){
			p->encoding=strdup(s);
			strupr(p->encoding);
		}
		s=pair_findnc(p->hdrs,"Content-Id");
		lib_strip_angle(s,bf);
		p->cid = strdup(bf);
		s=pair_findnc(p->hdrs,"Content-Location");
		p->cloc = strdup(s);
	}

	// Set some defaults for non RFC compliant messages
	if ((*parts)->type==NULL){
		prcc_decode_content(*parts,"text/plain");
	}
	if ((*parts)->subtype==NULL && strcmpnc((*parts)->type,"text")==0){
		(*parts)->subtype = strdup("plain");
	}
	
//	imsg("found %d parts (fld_id=%s mid=%d)\n",n,sw->fld_id,sw->mid);

	fseek(f,0,SEEK_SET);

	return TRUE;
}

int parts_ancestor_mixed(SwPart *part)
{
    SwPart *p=NULL;
    if(!part || !part->parent) return FALSE;    
    for (p=part->parent;p;p=p->parent){
        if(!p->type||!p->subtype) continue;
        if(strcmpnc(p->type, "multipart")!=0) continue;
        if(strcmpnc(p->subtype, "mixed")!=0) continue;
        return TRUE;
    }
    return FALSE;
}
// OK try and setup a nicer attachment determination mechanism if we can than surgeweb uses!!
void parts_process_for_attachments(SwPart *parts)
{
    SwPart *p;
    if(!parts)return;
    if(parts->attachments) return;  // Should only ever get run once to avoid duplicate entries
    for (p=parts;p;p=p->next){
        if (!p->parent) continue;
        if (!parts_ancestor_mixed(p)) continue;

        // may need to tweak the tests here!!
        // and also process for images and mpr if we can :-)
        if (!p->filename || lib_strlen(p->filename)==0) continue;
        
        // real attachment
        partslist_add_atend(&parts->attachments,p);
    }
}

