#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include <9pclient.h>
#include <plumb.h>
#include <ctype.h>
#include "dat.h"

char	*maildir = "Mail/";			/* mountpoint of mail file system */
char *mboxname = "mbox";			/* mailboxdir/mboxname is mail spool file */
char	*mailboxdir = nil;				/* nil == /mail/box/$user */
char *fsname;						/* filesystem for mailboxdir/mboxname is at maildir/fsname */
char	*user;
char	*outgoing;
char *srvname;

Window	*wbox;
Message	mbox;
Message	replies;
extern char		*home;
CFid		*plumbsendfd;
CFid		*plumbseemailfd;
CFid		*plumbshowmailfd;
CFid		*plumbsendmailfd;
Channel	*cplumb;
Channel	*cplumbshow;
Channel	*cplumbsend;
int		wctlfd;
void		mainctl(void*);
void		plumbproc(void*);
void		plumbshowproc(void*);
void		plumbsendproc(void*);
void		plumbthread(void);
void		plumbshowthread(void*);
void		plumbsendthread(void*);

int			shortmenu;

CFsys *mailfs;
CFsys *acmefs;

void
usage(void)
{
	fprint(2, "usage: Mail [-sS] [-n srvname] [-o outgoing] [mailboxname [directoryname]]\n");
	threadexitsall("usage");
}

void
removeupasfs(void)
{
	char buf[256];

	if(strcmp(mboxname, "mbox") == 0)
		return;
	snprint(buf, sizeof buf, "close %s", mboxname);
	fswrite(mbox.ctlfd, buf, strlen(buf));
}

int
ismaildir(char *s)
{
	Dir *d;
	int ret;

	d = fsdirstat(mailfs, s);
	if(d == nil)
		return 0;
	ret = d->qid.type & QTDIR;
	free(d);
	return ret;
}

void
threadmain(int argc, char *argv[])
{
	char *s, *name;
	char err[ERRMAX], *cmd;
	int i, newdir;
	Fmt fmt;

	doquote = needsrcquote;
	quotefmtinstall();

	/* open these early so we won't miss notification of new mail messages while we read mbox */
	if((plumbsendfd = plumbopenfid("send", OWRITE|OCEXEC)) == nil)
		fprint(2, "warning: open plumb/send: %r\n");
	if((plumbseemailfd = plumbopenfid("seemail", OREAD|OCEXEC)) == nil)
		fprint(2, "warning: open plumb/seemail: %r\n");
	if((plumbshowmailfd = plumbopenfid("showmail", OREAD|OCEXEC)) == nil)
		fprint(2, "warning: open plumb/showmail: %r\n");

	shortmenu = 0;
	srvname = "mail";
	ARGBEGIN{
	case 's':
		shortmenu = 1;
		break;
	case 'S':
		shortmenu = 2;
		break;
	case 'o':
		outgoing = EARGF(usage());
		break;
	case 'm':
		smprint(maildir, "%s/", EARGF(usage()));
		break;
	case 'n':
		srvname = EARGF(usage());
		break;
	default:
		usage();
	}ARGEND

	acmefs = nsmount("acme",nil);
	if(acmefs == nil)
		error("cannot mount acme: %r");
	mailfs = nsmount(srvname, nil);
	if(mailfs == nil)
		error("cannot mount %s: %r", srvname);

	name = "mbox";

	newdir = 1;
	if(argc > 0){
		i = strlen(argv[0]);
		if(argc>2 || i==0)
			usage();
		/* see if the name is that of an existing /mail/fs directory */
		if(argc==1 && argv[0][0] != '/' && ismaildir(argv[0])){
			name = argv[0];
			mboxname = estrdup(name);
			newdir = 0;
		}else{
			if(argv[0][i-1] == '/')
				argv[0][i-1] = '\0';
			s = strrchr(argv[0], '/');
			if(s == nil)
				mboxname = estrdup(argv[0]);
			else{
				*s++ = '\0';
				if(*s == '\0')
					usage();
				mailboxdir = argv[0];
				mboxname = estrdup(s);
			}
			if(argc > 1)
				name = argv[1];
			else
				name = mboxname;
		}
	}

	user = getenv("user");
	if(user == nil)
		user = "none";
	home = getenv("home");
	if(home == nil)
		home = getenv("HOME");
	if(home == nil)
		error("can't find $home");
	if(mailboxdir == nil)
		mailboxdir = estrstrdup(home, "/mail");
	if(outgoing == nil)
		outgoing = estrstrdup(mailboxdir, "/outgoing");

	mbox.ctlfd = fsopen(mailfs, estrstrdup(mboxname, "/ctl"), OWRITE);
	if(mbox.ctlfd == nil)
		error("can't open %s: %r", estrstrdup(mboxname, "/ctl"));

	fsname = estrdup(name);
	if(newdir && argc > 0){
		s = emalloc(5+strlen(mailboxdir)+strlen(mboxname)+strlen(name)+10+1);
		for(i=0; i<10; i++){
			sprint(s, "open %s/%s %s", mailboxdir, mboxname, fsname);
			if(fswrite(mbox.ctlfd, s, strlen(s)) >= 0)
				break;
			err[0] = '\0';
			errstr(err, sizeof err);
			if(strstr(err, "mbox name in use") == nil)
				error("can't create directory %s for mail: %s", name, err);
			free(fsname);
			fsname = emalloc(strlen(name)+10);
			sprint(fsname, "%s-%d", name, i);
		}
		if(i == 10)
			error("can't open %s/%s: %r", mailboxdir, mboxname);
		free(s);
	}

	s = estrstrdup(fsname, "/");
	mbox.name = estrstrdup(maildir, s);
	mbox.level= 0;
	readmbox(&mbox, maildir, s);
	home = getenv("home");
	if(home == nil)
		home = "/";

	wbox = newwindow();
	winname(wbox, mbox.name);
	wintagwrite(wbox, "Put Mail Delmesg ", 3+1+4+1+7+1);
	threadcreate(mainctl, wbox, STACK);

	fmtstrinit(&fmt);
	fmtprint(&fmt, "Mail");
	if(shortmenu)
		fmtprint(&fmt, " -%c", "sS"[shortmenu-1]);
	if(outgoing)
		fmtprint(&fmt, " -o %s", outgoing);
	fmtprint(&fmt, " %s", name);
	cmd = fmtstrflush(&fmt);
	if(cmd == nil)
		sysfatal("out of memory");
	winsetdump(wbox, "/acme/mail", cmd);
	mbox.w = wbox;

	mesgmenu(wbox, &mbox);
	winclean(wbox);

/*	wctlfd = open("/dev/wctl", OWRITE|OCEXEC);	/* for acme window */
	wctlfd = -1;
	cplumb = chancreate(sizeof(Plumbmsg*), 0);
	cplumbshow = chancreate(sizeof(Plumbmsg*), 0);
	if(strcmp(name, "mbox") == 0){
		/*
		 * Avoid creating multiple windows to send mail by only accepting
		 * sendmail plumb messages if we're reading the main mailbox.
		 */
		plumbsendmailfd = plumbopenfid("sendmail", OREAD|OCEXEC);
		cplumbsend = chancreate(sizeof(Plumbmsg*), 0);
		proccreate(plumbsendproc, nil, STACK);
		threadcreate(plumbsendthread, nil, STACK);
	}
	/* start plumb reader as separate proc ... */
	proccreate(plumbproc, nil, STACK);
	proccreate(plumbshowproc, nil, STACK);
	threadcreate(plumbshowthread, nil, STACK);
	fswrite(mbox.ctlfd, "refresh", 7);
	/* ... and use this thread to read the messages */
	plumbthread();
}

void
plumbproc(void* v)
{
	Plumbmsg *m;

	threadsetname("plumbproc");
	for(;;){
		m = plumbrecvfid(plumbseemailfd);
		sendp(cplumb, m);
		if(m == nil)
			threadexits(nil);
	}
}

void
plumbshowproc(void* v)
{
	Plumbmsg *m;

	threadsetname("plumbshowproc");
	for(;;){
		m = plumbrecvfid(plumbshowmailfd);
		sendp(cplumbshow, m);
		if(m == nil)
			threadexits(nil);
	}
}

void
plumbsendproc(void* v)
{
	Plumbmsg *m;

	threadsetname("plumbsendproc");
	for(;;){
		m = plumbrecvfid(plumbsendmailfd);
		sendp(cplumbsend, m);
		if(m == nil)
			threadexits(nil);
	}
}

void
newmesg(char *name, char *digest)
{
	Dir *d;

	if(strncmp(name, mbox.name, strlen(mbox.name)) != 0)
		return;	/* message is about another mailbox */
	if(mesglookupfile(&mbox, name, digest) != nil)
		return;
	if(strncmp(name, "Mail/", 5) == 0)
		name += 5;
	d = fsdirstat(mailfs, name);
	if(d == nil)
		return;
	if(mesgadd(&mbox, mbox.name, d, digest))
		mesgmenunew(wbox, &mbox);
	free(d);
}

void
showmesg(char *name, char *digest)
{
	char *n;
	char *mb;

	mb = mbox.name;
	if(strncmp(name, mb, strlen(mb)) != 0)
		return;	/* message is about another mailbox */
	n = estrdup(name+strlen(mb));
	if(n[strlen(n)-1] != '/')
		n = egrow(n, "/", nil);
	mesgopen(&mbox, mbox.name, name+strlen(mb), nil, 1, digest);
	free(n);
}

void
delmesg(char *name, char *digest, int dodel, char *save)
{
	Message *m;

	m = mesglookupfile(&mbox, name, digest);
	if(m != nil){
		if(save)
			mesgcommand(m, estrstrdup("Save ", save));
		if(dodel)
			mesgmenumarkdel(wbox, &mbox, m, 1);
		else{
			/* notification came from plumber - message is gone */
			mesgmenudel(wbox, &mbox, m);
			if(!m->opened)
				mesgdel(&mbox, m);
		}
	}
}

void
plumbthread(void)
{
	Plumbmsg *m;
	Plumbattr *a;
	char *type, *digest;

	threadsetname("plumbthread");
	while((m = recvp(cplumb)) != nil){
		a = m->attr;
		digest = plumblookup(a, "digest");
		type = plumblookup(a, "mailtype");
		if(type == nil)
			fprint(2, "Mail: plumb message with no mailtype attribute\n");
		else if(strcmp(type, "new") == 0)
			newmesg(m->data, digest);
		else if(strcmp(type, "delete") == 0)
			delmesg(m->data, digest, 0, nil);
		else
			fprint(2, "Mail: unknown plumb attribute %s\n", type);
		plumbfree(m);
	}
	threadexits(nil);
}

void
plumbshowthread(void *v)
{
	Plumbmsg *m;

	USED(v);
	threadsetname("plumbshowthread");
	while((m = recvp(cplumbshow)) != nil){
		showmesg(m->data, plumblookup(m->attr, "digest"));
		plumbfree(m);
	}
	threadexits(nil);
}

void
plumbsendthread(void *v)
{
	Plumbmsg *m;

	USED(v);
	threadsetname("plumbsendthread");
	while((m = recvp(cplumbsend)) != nil){
		mkreply(nil, "Mail", m->data, m->attr, nil);
		plumbfree(m);
	}
	threadexits(nil);
}

int
mboxcommand(Window *w, char *s)
{
	char *args[10], **targs, *save;
	Window *sbox;
	Message *m, *next;
	int ok, nargs, i, j;
	CFid *searchfd;
	char buf[128], *res;

	nargs = tokenize(s, args, nelem(args));
	if(nargs == 0)
		return 0;
	if(strcmp(args[0], "Mail") == 0){
		if(nargs == 1)
			mkreply(nil, "Mail", "", nil, nil);
		else
			mkreply(nil, "Mail", args[1], nil, nil);
		return 1;
	}
	if(strcmp(s, "Del") == 0){
		if(mbox.dirty){
			mbox.dirty = 0;
			fprint(2, "mail: mailbox not written\n");
			return 1;
		}
		if(w != mbox.w){
			windel(w, 1);
			return 1;
		}
		ok = 1;
		for(m=mbox.head; m!=nil; m=next){
			next = m->next;
			if(m->w){
				if(windel(m->w, 0))
					m->w = nil;
				else
					ok = 0;
			}
		}
		for(m=replies.head; m!=nil; m=next){
			next = m->next;
			if(m->w){
				if(windel(m->w, 0))
					m->w = nil;
				else
					ok = 0;
			}
		}
		if(ok){
			windel(w, 1);
			removeupasfs();
			threadexitsall(nil);
		}
		return 1;
	}
	if(strcmp(s, "Put") == 0){
		rewritembox(wbox, &mbox);
		return 1;
	}
	if(strcmp(s, "Get") == 0){
		fswrite(mbox.ctlfd, "refresh", 7);
		return 1;
	}
	if(strcmp(s, "Delmesg") == 0){
		save = nil;
		if(nargs > 1)
			save = args[1];
		s = winselection(w);
		if(s == nil)
			return 1;
		nargs = 1;
		for(i=0; s[i]; i++)
			if(s[i] == '\n')
				nargs++;
		targs = emalloc(nargs*sizeof(char*));	/* could be too many for a local array */
		nargs = getfields(s, targs, nargs, 1, "\n");
		for(i=0; i<nargs; i++){
			if(!isdigit(targs[i][0]))
				continue;
			j = atoi(targs[i]);	/* easy way to parse the number! */
			if(j == 0)
				continue;
			snprint(buf, sizeof buf, "%s%d", mbox.name, j);
			delmesg(buf, nil, 1, save);
		}
		free(s);
		free(targs);
		return 1;
	}
	if(strcmp(s, "Search") == 0){
		if(nargs <= 1)
			return 1;
		s = estrstrdup(mboxname, "/search");
		searchfd = fsopen(mailfs, s, ORDWR);
		if(searchfd == nil)
			return 1;
		save = estrdup(args[1]);
		for(i=2; i<nargs; i++)
			save = eappend(save, " ", args[i]);
		fswrite(searchfd, save, strlen(save));
		fsseek(searchfd, 0, 0);
		j = fsread(searchfd, buf, sizeof buf - 1);
 		if(j == 0){
			fprint(2, "[%s] search %s: no results found\n", mboxname, save);
			fsclose(searchfd);
			free(save);
			return 1;
		}
		free(save);
		buf[j] = '\0';
		res = estrdup(buf);
		j = fsread(searchfd, buf, sizeof buf - 1);
		for(; j != 0; j = fsread(searchfd, buf, sizeof buf - 1), buf[j] = '\0')
			res = eappend(res, "", buf);
		fsclose(searchfd);

		sbox = newwindow();
		winname(sbox, s);
		free(s);
		threadcreate(mainctl, sbox, STACK);
		winopenbody(sbox, OWRITE);

		/* show results in reverse order */
		m = mbox.tail;
		save = nil;
		for(s=strrchr(res, ' '); s!=nil || save!=res; s=strrchr(res, ' ')){
			if(s != nil){
				save = s+1;
				*s = '\0';
			}
			else save = res;
			save = estrstrdup(save, "/");
			for(; m && strcmp(save, m->name) != 0; m=m->prev);
			free(save);
			if(m == nil)
				break;
			fsprint(sbox->body, "%s%s\n", m->name, info(m, 0, 0));
			m = m->prev;
		}
		free(res);
		winclean(sbox);
		winclosebody(sbox);
		return 1;
	}
	return 0;
}

void
mainctl(void *v)
{
	Window *w;
	Event *e, *e2, *eq, *ea;
	int na, nopen;
	char *s, *t, *buf;

	w = v;
	winincref(w);
	proccreate(wineventproc, w, STACK);

	for(;;){
		e = recvp(w->cevent);
		switch(e->c1){
		default:
		Unknown:
			print("unknown message %c%c\n", e->c1, e->c2);
			break;

		case 'E':	/* write to body; can't affect us */
			break;

		case 'F':	/* generated by our actions; ignore */
			break;

		case 'K':	/* type away; we don't care */
			break;

		case 'M':
			switch(e->c2){
			case 'x':
			case 'X':
				ea = nil;
				e2 = nil;
				if(e->flag & 2)
					e2 = recvp(w->cevent);
				if(e->flag & 8){
					ea = recvp(w->cevent);
					na = ea->nb;
					recvp(w->cevent);
				}else
					na = 0;
				s = e->b;
				/* if it's a known command, do it */
				if((e->flag&2) && e->nb==0)
					s = e2->b;
				if(na){
					t = emalloc(strlen(s)+1+na+1);
					sprint(t, "%s %s", s, ea->b);
					s = t;
				}
				/* if it's a long message, it can't be for us anyway */
				if(!mboxcommand(w, s))	/* send it back */
					winwriteevent(w, e);
				if(na)
					free(s);
				break;

			case 'l':
			case 'L':
				buf = nil;
				eq = e;
				if(e->flag & 2){
					e2 = recvp(w->cevent);
					eq = e2;
				}
				s = eq->b;
				if(eq->q1>eq->q0 && eq->nb==0){
					buf = emalloc((eq->q1-eq->q0)*UTFmax+1);
					winread(w, eq->q0, eq->q1, buf);
					s = buf;
				}
				nopen = 0;
				do{
					/* skip 'deleted' string if present' */
					if(strncmp(s, deleted, strlen(deleted)) == 0)
						s += strlen(deleted);
					/* skip mail box name if present */
					if(strncmp(s, mbox.name, strlen(mbox.name)) == 0)
						s += strlen(mbox.name);
					nopen += mesgopen(&mbox, mbox.name, s, nil, 0, nil);
					while(*s!='\0' && *s++!='\n')
						;
				}while(*s);
				if(nopen == 0)	/* send it back */
					winwriteevent(w, e);
				free(buf);
				break;

			case 'I':	/* modify away; we don't care */
			case 'D':
			case 'd':
			case 'i':
				break;

			default:
				goto Unknown;
			}
		}
	}
}
