/* $Id: cue.c,v 1.101 2015/09/29 13:17:42 onoe Exp $ */

/*-
 * Copyright (c) 1998-2001 Atsushi Onoe
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>

#include "cue.h"

struct state statebuf;

#define	NEXTKEY(str)	{		\
	move(LINES-1, 0);		\
	addstr(str);			\
	clrtoeol();			\
	refresh();			\
	(void)cangetch(-1);		\
}

static int
checkmopt(struct state *state, const char *action)
{
	if (state->config.moption) {
		snprintf(state->status, sizeof(state->status),
		    "%s is disabled by -m option", action);
		beep();
		return -1;
	}
	return 0;
}

/*ARGSUSED*/
int
main(int argc, char **argv)
{
	int ch;
	int i, j;
	struct state *state = &statebuf;
	struct window *win;
	struct folder *folder;
	struct msginfo *msg;
	char *p;
	int useidl = 0;
	int nowrap = 0;
	extern char *optarg;
	extern int optind;
	extern int eucjp;

	memset(state, 0, sizeof(*state));
	if ((p = getenv("HOME")))
		(void)chdir(p);
	if ((p = getenv("MAILDIR")) == NULL)
		p = "Mail";
	
	while ((ch = getopt(argc, argv, "em:sw")) != EOF) {
		switch (ch) {
		case 'm':
			state->config.moption = 1;
			p = optarg;
			break;
		case 's':
			useidl++;
			break;
		case 'w':
			nowrap++;
			break;
		case 'e':
			eucjp++;
			break;
		default:
			fprintf(stderr, "Usage: cue [-m MailDir] [-s] [-w] [+folder]\n");
			exit(1);
		}
	}
	if (chdir(p) < 0) {
		perror(p);
		exit(1);
	}

	decode_init();
	conf_update(state);

	if ((p = argv[optind]) == NULL)
		p = state->config.folder;
	state->folder = folder_open(p, 1);
	if (state->folder == NULL) {
		fprintf(stderr, "cannot open %s\n", p);
		exit(1);
	}
	state->folder->pos = state->folder->nmsg - 1;

	disp_init();
	if (useidl)
		idlok(stdscr, TRUE);
	if (!nowrap)
		wrapok(stdscr, TRUE);
	disp_msgmode(state, 0);

  waitfor:
	if (proc_running(PTYPE_INC)) {
		if (state->status[0] == '\0')
			strlcpy(state->status, "Incing ...", sizeof(state->status));
		disp_update(state);
		folder = state->folder;
		if (!cangetch(500)) {
			i = folder->nmsg;
			folder_update(folder, 0);
			if (folder->nmsg > i)
				folder->nmsg--;	/* XXX: now incing */
			if (folder->incpos > 0 && folder->nmsg > folder->incpos) {
				folder->pos = folder->incpos;
				folder->incpos = -1;
			}
			goto waitfor;
		}
		folder->incpos = -1;
	} else if (state->config.disptime) {
		disp_update(state);
		while (!cangetch(30 * 1000)) {
			disp_time(state);
			refresh();
		}
	} else {
		disp_update(state);
		cangetch(-1);	/* wait */
	}
	state->status[0] = '\0';

	for (;;) {
		ch = getch();
		if (ch == ERR)
			goto waitfor;
		win = &state->folwin;
		folder = state->folder;
		switch (ch) {
		case 'q':
			if ((folder = folder_modified())) {
				beep();
				if (!edit_yn(state, "Buffer %s is modified: quit anyway ? ", CP(&folder->name)))
					goto waitfor;
			}
			endwin();
			exit(0);
		case CTRL('z'):
			kill(getpid(), SIGTSTP);
			break;
		case CTRL('\\'):
			kill(getpid(), SIGABRT);
			break;
		case 'h':
			disp_help(state);
			break;
		case CTRL('l'):
			disp_redraw(state);
			break;
		case '\b':
		case CTRL('b'):
		case CTRL('f'):
		case CTRL('u'):
		case CTRL('d'):
		case CTRL('y'):
		case CTRL('e'):
		case '[':
		case ']':
			if (!state->msgmode || state->message == NULL) {
				strlcpy(state->status, "Please show the current message first", sizeof(state->status));
				break;
			}

		    {
			int move_d = 0, dir = 1;

			switch (ch) {
			case '\b':
			case CTRL('b'):
				dir = -1;
				/* FALLTHRU */
			case CTRL('f'):
				move_d = state->msgwin.lines;
				break;
			case CTRL('u'):
				dir = -1;
				/* FALLTHRU */
			case CTRL('d'):
				move_d = (state->msgwin.lines / 2);
				break;
			case CTRL('y'):
				dir = -1;
				/* FALLTHRU */
			case CTRL('e'):
				move_d = 1;
				break;
			case '[':
				move_d = state->msgwin.off;
				dir = -1;
				break;
			case ']':
				/*
				 * state->message.lines is not kept constant,
				 * so I can't calculate valid delta to move.
				 *	ref.
				 *	move delta = state->message.lines
				 *			- state->msgwin.off;
				 */
				for (move_d = state->msgwin.lines;
				     fdb_read(state->message, move_d);
				     move_d += state->msgwin.lines)
					;
				break;
			}

			while (move_d--) {
				if (! fdb_read(state->message, state->msgwin.off + dir))
					break;

				/*
				 * This is viewing issue, so you can remove
				 * the below code if you dislike.
				 * This code is from disp.c:disp_update().
				 */
				if (dir > 0
				 && state->message->skip_lines <= state->msgwin.off
				 && disp_getmore(state->message, state->msgwin.off + state->msgwin.lines) > 100)
						break;

				/* calculate actually. */
				state->msgwin.off += dir;
			}
		    }
			break;
		case ' ':
			if (!state->msgmode) {
				disp_msgmode(state, 1);
				state->message = NULL;
				message_open(state, 1);
			}
			state->msgwin.off += state->msgwin.lines;
			if (state->message &&
			    fdb_read(state->message, state->msgwin.off))
				break;
			/* FALLTHRU */
		case 'n':
		case 'N':
			message_next(state, 1, (ch == 'N'));
			state->prevdir = 1;
			break;
		case 'p':
		case 'P':
			message_next(state, -1, (ch == 'P'));
			state->prevdir = -1;
			break;
		case CTRL('n'):
  ctrl_n:
			if (folder->pos < folder->nmsg - 1)
				folder->pos++;
			else
				strlcpy(state->status, "No more message", sizeof(state->status));
			break;
		case CTRL('p'):
  ctrl_p:
			if (folder->pos > 0)
				folder->pos--;
			else
				strlcpy(state->status, "No more message", sizeof(state->status));
			break;
		case CTRL('v'):
  ctrl_v:
			if (win->off + win->lines >= folder->nmsg) {
				strlcpy(state->status, "End of buffer", sizeof(state->status));
				break;
			}
			win->off += win->lines;
			if (folder->pos < win->off)
				folder->pos = win->off;
			break;
		case '<':
			folder->pos = win->off = 0;
			if (state->msgmode)
				state->message = NULL;
				message_open(state, 1);
			break;
		case '>':
			folder->pos = folder->nmsg - 1;
			if (state->msgmode) {
				disp_center(state);
				state->message = NULL;
				message_open(state, 1);
			}
			break;
		case 't':
			if (state->msgmode) {
				disp_msgmode(state, 0);
			} else {
				disp_msgmode(state, 1);
				message_open(state, 0);
			}
			break;
		case '.':
			disp_msgmode(state, 1);
			state->message = NULL;
			message_open(state, 1);
			break;
		case 'g':
			folder_change(state);
			break;
		case 'j':
			message_jump(state);
			break;
		case 's':
			if (folder->nmark) {
				snprintf(state->status, sizeof(state->status),
				    "Folder %s is modified", CP(&folder->name));
				beep();
				break;
			}
			folder_update(folder, 1);
			break;
		case 'Y':
		case 'y':
			save_part(state, ch == 'Y');
			break;
		case 'E':
			helper_exec(state);
			break;
		case '/':
		case '?':
			if (!state->msgmode || state->message == NULL) {
				strlcpy(state->status, "Please show the current message first", sizeof(state->status));
				break;
			}
			isearch(state, state->message, (ch == '/' ? 1 : -1));
			break;
		case CTRL('s'):
			isearch(state, NULL, 1);
			break;
		case CTRL('r'):
			isearch(state, NULL, -1);
			break;
		case 'u':
			msg = &folder->msg[folder->pos];
			if (msg->mark == 0) {
				if (state->message &&
				    (state->message->flags & FDB_DECRYPTED)) {
					fdb_purge(NULL);
					folder_purge(state, msg->num);
					state->message = NULL;
					message_open(state, 1);	/* reopen */
				} else
					strlcpy(state->status, "No mark", sizeof(state->status));
			} else {
				if (msg->mark == MARK_REFILE)
					msg->refile[0] = NULL;
				msg->mark = 0;
				folder->nmark--;
			}
			break;
		case 'd':
		case '*':
		case '-':
			msg = &folder->msg[folder->pos];
			if (msg->mark == MARK_DELETE) {
				strlcpy(state->status, "Already marked as delete", sizeof(state->status));
				break;
			}
			if (msg->mark == MARK_REFILE) {
				strlcpy(state->status, "Already marked as refile", sizeof(state->status));
				break;
			}
			if (msg->mark == 0)
				folder->nmark++;
			if (ch == 'd') {
				msg->mark = MARK_DELETE;
				message_next(state, 0, 0);
			} else if (ch == '-') {
				msg->mark = MARK_SPAM;
				message_next(state, 0, 0);
			} else {
				msg->mark = MARK_MARK;
			}
			break;
		case 'o':
		case '^':
			msg = &folder->msg[folder->pos];
			if (msg->mark == MARK_DELETE) {
				strlcpy(state->status, "Already marked as delete", sizeof(state->status));
				break;
			}
			if (refile_folder(state))
				message_next(state, 0, 0);
			break;
		case '!':
			msg = &folder->msg[folder->pos];
			if (refile_again(state, msg))
				message_next(state, 0, 0);
			break;
		case 'x':
			exec_mark(state);
			break;
		case 'i':
			if (checkmopt(state, "inc"))
				break;
			if ((folder = folder_open(state->config.folder, 1))) {
				state->folder = folder;
				folder->pos = folder->nmsg - 1;
				state->message = NULL;
				state->prevdir = 1;
				exec_inc(state);
			}
			break;
		case 'O':
			exec_pack(state);
			break;
		case 'S':
			exec_sort(state);
			break;
		case 'w':
			if (checkmopt(state, "compose"))
				break;
			compose(state);
			break;
		case 'a':
			if (checkmopt(state, "reply"))
				break;
			reply(state, COMP_REPLYANK);
			break;
		case 'f':
			if (checkmopt(state, "forward"))
				break;
			forward(state, 1);
			break;
		case 'A':
		case 'F':
			for (i = 0, msg = folder->msg; i < folder->nmsg; i++, msg++) {
				if (msg->mark == MARK_MARK)
					break;
			}
			if (i == folder->nmsg) {
				strlcpy(state->status, "No mark", sizeof(state->status));
				break;
			}
			switch (ch) {
			case 'A':
				if (checkmopt(state, "reply"))
					break;
				reply(state, COMP_REPLYMARK);
				break;
			case 'F':
				if (checkmopt(state, "forward"))
					break;
				forward_mark(state);
				break;
			}
			for (i = 0, msg = folder->msg; i < folder->nmsg; i++, msg++) {
				if (msg->mark == MARK_MARK) {
					msg->mark = 0;
					folder->nmark--;
				}
			}
			break;
		case 'V':
#ifdef USE_SMIME
			if (smime_verify(state))
				break;
#endif /* USE_SMIME */
#ifdef USE_PGPMIME
			if (pgpmime_verify(state))
				break;
#endif /* USE_PGPMIME */
#ifdef USE_UUDES
			if (uudes_decrypt(state))
				break;
#endif /* USE_UUDES */
			break;
		case 'e':
			if (checkmopt(state, "edit"))
				break;
			NEXTKEY("e-");
			ch = getch();
			if (!(folder->flags & FOL_DRAFT)) {
				strlcpy(state->status, "Not draft folder", sizeof(state->status));
				break;
			}
			message_open(state, 0);
			if (state->message == NULL) {
				strlcpy(state->status, "Message not found", sizeof(state->status));
				break;
			}
			switch (ch) {
			case 'E':
			case 'e':
				mpart_edit(state, ch == 'E');
				break;
			case 's':
				mpart_single(state);
				break;
			case 'm':
				mpart_multi(state);
				break;
			case 'a':
				mpart_append(state, NULL, NULL);
				break;
			case 'c':
				mpart_append(state, "*", NULL);
				break;
			case 'd':
				mpart_delete(state);
				break;
			case 'u':
				mpart_undo(state);
				break;
#ifdef USE_SMIME
			case 'S':
				smime_sign(state);
				break;
			case CTRL('s'):
				smime_encrypt(state);
				break;
#endif /* USE_SMIME */
#ifdef USE_PGPMIME
			case 'P':
				pgpmime_sign(state);
				break;
			case CTRL('e'):
				pgpmime_encrypt(state);
				break;
#endif /* USE_PGPMIME */
			case CTRL('g'):
				strlcpy(state->status, "Quit", sizeof(state->status));
				break;
			default:
				beep();
				break;
			}
			break;
		case 'c':
		case 'C':
			if (folder->flags & FOL_DRAFT)
				exec_send(state, ch == 'C');
			else
				strlcpy(state->status, "Not draft folder", sizeof(state->status));
			break;
		case 'm':
			NEXTKEY("m-");
			switch (ch = getch()) {
			case 'a':	/* mark all */
				for (i = 0, msg = folder->msg; i < folder->nmsg; i++, msg++) {
					if (!msg->mark) {
						msg->mark = MARK_MARK;
						folder->nmark++;
					}
				}
				break;
			case 'd':	/* mark delete */
				j = 0;
				for (i = 0, msg = folder->msg; i < folder->nmsg; i++, msg++) {
					if (msg->mark == MARK_MARK) {
						msg->mark = MARK_DELETE;
						j++;
					}
				}
				if (j == 0)
					strlcpy(state->status, "No mark", sizeof(state->status));
				break;
			case '-':	/* mark spam */
				j = 0;
				for (i = 0, msg = folder->msg; i < folder->nmsg; i++, msg++) {
					if (msg->mark == MARK_MARK) {
						msg->mark = MARK_SPAM;
						j++;
					}
				}
				if (j == 0)
					strlcpy(state->status, "No mark", sizeof(state->status));
				break;
			case 's':
				search_mark_folder(state, 1);
				break;
			case 'r':
				search_mark_folder(state, -1);
				break;
			case 'S':
				rguess_mark_folder(state, 1);
				break;
			case 'R':
				rguess_mark_folder(state, -1);
				break;
			case '/':
				pick_mark_folder(state, 1);
				break;
			case '?':
				pick_mark_folder(state, -1);
				break;
			case 'o':	/* mark refile */
			case '^':
				msg = &folder->msg[folder->pos];
				if (msg->mark != MARK_MARK) {
					strlcpy(state->status, "Not marked", sizeof(state->status));
					break;
				}
				if (!refile_folder(state))
					break;
				for (i = 0, msg = folder->msg; i < folder->nmsg; i++, msg++) {
					if (msg->mark == MARK_MARK)
						refile_again(state, msg);
				}
				break;
			case 'O':	/* mark refile guessed */
				if (folder->nmark == 0) {
					strlcpy(state->status, "No mark", sizeof(state->status));
					break;
				}
				for (i = 0, msg = folder->msg; i < folder->nmsg; i++, msg++) {
					if (msg->mark == MARK_MARK) {
						folder->pos = i;
						message_open(state, 1);
						if (state->message != NULL
						&&  refile_guess(state, msg))
							msg->mark = MARK_REFILE;
					}
				}
				break;
			case 'u':	/* mark undo mark */
				for (i = 0, msg = folder->msg; i < folder->nmsg; i++, msg++) {
					if (msg->mark == MARK_MARK) {
						msg->mark = 0;
						folder->nmark--;
					}
				}
				break;
			case 'U':	/* mark undo any mark */
				for (i = 0, msg = folder->msg; i < folder->nmsg; i++, msg++) {
					if (msg->mark) {
						if (msg->mark == MARK_REFILE)
							msg->refile[0] = NULL;
						msg->mark = 0;
					}
				}
				folder->nmark = 0;
				break;
			case CTRL('g'):
				strlcpy(state->status, "Quit", sizeof(state->status));
				break;
			default:
				beep();
				break;
			}
			break;
		case '\033':
			NEXTKEY("ESC-");
			switch (ch = getch()) {
			case '[':
				NEXTKEY("ESC-[-");
				switch (ch = getch()) {
				case 'A':
					goto ctrl_p;
				case 'B':
					goto ctrl_n;
				case '5':
					NEXTKEY("ESC-[-5-");
					switch (ch = getch()) {
					case '~':
						goto esc_v;
					}
					break;
				case '6':
					NEXTKEY("ESC-[-6-");
					switch (ch = getch()) {
					case '~':
						goto ctrl_v;
					}
					break;
				}
				beep();
				break;
			case 'v':
  esc_v:
				if (win->off == 0) {
					strlcpy(state->status, "Beginning of buffer", sizeof(state->status));
					break;
				}
				win->off -= win->lines;
				if (win->off < 0)
					win->off = 0;
				if (folder->pos >= win->off + win->lines)
					folder->pos = win->off + win->lines - 1;
				break;
			case '<':
  esc_home:
				folder->pos = win->off = 0;
				break;
			case '>':
  esc_end:
				folder->pos = folder->nmsg - 1;
				break;
			case 'a':
				if (checkmopt(state, "reply"))
					break;
				reply(state, COMP_REPLORIG);
				break;
			case 'f':
				if (checkmopt(state, "forward"))
					break;
				forward(state, 0);
				break;
			case 'p':
				exec_pack(state);	/* y-or-n? */
				break;
			case 'r':	/* read marked file. */
				if (checkmopt(state, "import mark"))
					break;
				import_mark(state);
				break;
			case 'w':	/* write marked file. */
				if (checkmopt(state, "export mark"))
					break;
				export_mark(state);
				break;
			case CTRL('g'):
				strlcpy(state->status, "Quit", sizeof(state->status));
				break;
			case 'O':
				NEXTKEY("ESC-[-O-");
				switch (ch = getch()) {
				case 'H':
					goto esc_home;
				case 'F':
					goto esc_end;
				}
				break;
			default:
				beep();
				break;
			}
			break;
		case CTRL('x'):
			NEXTKEY("C-x-");
			switch (ch = getch()) {
			case '\n':
			case '\r':
				if (checkmopt(state, "compose"))
					break;
				compose(state);
				break;
			case 'w':
				disp_setlines(state);
				break;
			case CTRL('g'):
				strlcpy(state->status, "Quit", sizeof(state->status));
				break;
			case 'M':
				message_debug(state);
				break;
			case 'e':
				message_encoding(state);
				break;
			case 'a':
				state->nomime ^= 1;
				snprintf(state->status, sizeof(state->status),
				    "%s MIME Analysis",
				    (state->nomime ? "Skip" : "Do"));
				msg = &folder->msg[folder->pos];
				folder_purge(state, msg->num);
				fdb_purge(NULL);
				state->message = NULL;
				message_open(state, 1);	/* reopen */
				if (!folder->nmark)
					folder_update(folder, 1);
				break;
			default:
				beep();
				break;
			}
			break;
		default:
			beep();
			break;
		}
	}
	/*NOTREACHED*/
}
