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

/*-
 * Copyright (c) 1998-2000 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 <sys/mman.h>
#include <sys/stat.h>

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <fcntl.h>
#include <time.h>

#include "cue.h"

void
disp_init(void)
{
	initscr();
	scrollok(stdscr, TRUE);
	raw();
	noecho();
	nonl();
	nodelay(stdscr, TRUE);
}

void
disp_msgmode(struct state *state, int on)
{
	state->msgmode = on;
}

/* TODO: Performance */
static int
disp_multipart(struct state *state, int *cursyp, int off)
{
	int npart, cpart;
	struct filedb *fdb, *tfdb;
	cbuf_t cbuf;
	ccbuf_t *cp;
	int cursy, lines;
	time_t dt;
	struct tm *tm;
	int i, w;

	npart = 0;
	lines = state->folwin.lines;
	fdb = state->message;
	cursy = *cursyp;

	while (fdb->uppart)
		fdb = fdb->uppart;
	if (fdb == state->message)
		cpart = 0;
	else
		cpart = -1;
	while ((fdb = fdb->nextpart) != NULL) {
		if (fdb->flags & FDB_HIDDEN)
			continue;
		if (fdb->prevpart->flags & FDB_INLINE)
			continue;

		if (npart++ < off)
			continue;
		if (cursy + npart - off >= lines) {
			if (npart - off >= lines) {
				if (cpart >= 0) {
					npart--;
					break;
				}
			}
			/* ugh */
			continue;
		}
		if (fdb == state->message)
			cpart = npart;
		move(cursy + npart - off, 0);
		switch (fdb->flags & FDB_ENCODE) {
		case FDB_ENC_Q:		addch('Q');	break;
		case FDB_ENC_B64:	addch('B');	break;
		case FDB_ENC_GZ64:	addch('G');	break;
		case FDB_ENC_UU:	addch('U');	break;
		case FDB_ENC_BINHEX:	addch('H');	break;
		case FDB_ENC_ZIP:	addch('Z');	break;
		default:		addch(' ');	break;
		}
		addstr("     ");
		addch(message_note(fdb));
		addch(' ');
		for (i = 1; i <= fdb->partdepth; i++) {
			for (tfdb = fdb; tfdb->uppart; tfdb = tfdb->uppart) {
				if (tfdb->partdepth <= i)
					break;
			}
			printw("%d", tfdb->partnum + 1);
			if (tfdb != fdb)
				addch('.');
		}
		addch(' ');
		addch(' ');
		if (fdb->flags & FDB_MAIL) {
			cp = &fdb->hdr_val[HT_DATE];
			if (CP(cp) == NULL || (dt = parse_date(cp)) < 0) {
				addstr("?????? ");
			} else {
				if (fdb->filetime <= 0)
					fdb->filetime = dt;
				tm = localtime(&dt);
				printw("%02d%02d%02d ", tm->tm_year % 100, tm->tm_mon + 1, tm->tm_mday);
			}
			w = 7;
			cp = &fdb->hdr_val[HT_FROM];
			if (CP(cp))
				(void)message_parse_addr(CP(cp), CE(cp), (ccbuf_t *)&cbuf);
			else
				cbuf.ptr = NULL;
			if (cbuf.ptr == NULL || conf_myaddr(state, (ccbuf_t *)&cbuf)) {
				cp = &fdb->hdr_val[HT_TO];
				(void)message_parse_addr(CP(cp), CE(cp), (ccbuf_t *)&cbuf);
				addstr("To:");
				w += 3;
			}
			if (cbuf.len >= 21 - w)
				cbuf.len = 21 - w;
			addnstr(cbuf.ptr, cbuf.len);
			w += cbuf.len;
		} else {
			w = fdb->type.len;
			if (w > 22)
				w = 22;
			addnstr(fdb->type.ptr, w);
		}
		do {
			addch(' ');
		} while (w++ < 22);
		cp = &fdb->hdr_val[HT_SUBJECT];
		if (cp->ptr)
			addnstr(cp->ptr, cp->len);
		clrtoeol();
	}
	*cursyp += cpart - off;
	return npart;
}

static void
disp_setwinsize(struct state *state)
{
	int lines;

	lines = LINES - 1;
	if (lines < 7)
		abort();	/* XXX: cannot show */
	if (state->help) {
		state->hlpwin.y = lines / 2;
		state->hlpwin.lines = lines - state->hlpwin.y - 1;
		lines -= (state->hlpwin.lines + 1);
	} else {
		state->hlpwin.y = 0;
		state->hlpwin.lines = 0;
	}
	if (state->msgmode && state->message) {
		state->folwin.y = 0;
		if (state->config.linesall)
			state->folwin.lines = (lines * state->config.lines + state->config.linesall / 2) / state->config.linesall;
		else
			state->folwin.lines = state->config.lines;
		if (state->folwin.lines < 1)
			state->folwin.lines = 1;
		else if (state->folwin.lines + 3 > lines)
			state->folwin.lines = lines - 3;
		state->msgwin.y = state->folwin.lines + 1;
		state->msgwin.lines = lines - state->msgwin.y - 1;
	} else {
		state->folwin.y = 0;
		state->folwin.lines = lines - 1;
		state->msgwin.y = 0;
		state->msgwin.lines = 0;
	}
}

void
disp_update(struct state *state)
{
	int i, type;
	ccbuf_t *buf;
	struct window *win;
	struct filedb *fdb, *tfdb;
	struct folder *fl;
	int more, last;
	int newoff;
	int cursy;
	int pos;
	int npart;
	int lastpart = 0;
	char nomime;
	char matchbuf[LINE_WIDTH + 1];

	type = proc_getstate(PTYPE_ANY, NULL,
	    state->status, sizeof(state->status));
	if (type == PTYPE_INC) {
		if (state->status[0]) {
			if (strstr(state->status, "no mail to incorporate"))
				strlcpy(state->status, "No new message", sizeof(state->status));
		} else
			strlcpy(state->status, "Incing ... done",
			    sizeof(state->status));
		/* force numscan */
		memset(&state->folder->stbuf, 0, sizeof(state->folder->stbuf));
		folder_update(state->folder, 0);
		if (state->folder->incpos > 0
		&&  state->folder->nmsg > state->folder->incpos) {
			state->folder->pos = state->folder->incpos;
			state->folder->incpos = -1;
		}
	}

	disp_setwinsize(state);
	fl = state->folder;
	pos = fl->pos;

	win = &state->folwin;
	if (pos < win->off || pos >= win->off + win->lines) {
		newoff = pos - (win->lines - 1) / 2;	/* centering */
		if (newoff < 0)
			newoff = 0;
		if (state->msgwin.lines == 0) {	/*XXX*/
			i = newoff - win->off;
			if (i > -win->lines && i < win->lines)
				scrl(i);
		}
		win->off = newoff;
	}
	cursy = pos - win->off;
	nomime = state->nomime ? 'N' : '%';

	move(win->y + win->lines, 0);
	attron(A_REVERSE);
	for (i = pos + 1, more = 0; i < fl->nmsg; i++)
		if (!fl->msg[i].mark)
			more++;
	printw("--%c%%-Cue: %s  (Summary)--[%d more]",
		nomime, fl->name.ptr, more);
	hline(A_REVERSE|'-', COLS);

	if (state->msgwin.lines) {
		for (i = 0; i < MAX_REFILE; i++) {
			if (fl->msg[pos].refile[i] == NULL)
				break;
			if (i == 0)
				addstr("--(");
			else
				addch(',');
			addstr(fl->msg[pos].refile[i]);
		}
		if (i > 0)
			addch(')');
		move(state->msgwin.y + state->msgwin.lines, 0);
		printw("--%c%%-Cue: %s", nomime, state->message->name);
		fdb = state->message;
		for (i = 1; i <= fdb->partdepth; i++) {
			if (i == 1)
				addstr(" =");
			else
				addch('.');
			for (tfdb = fdb; tfdb->uppart; tfdb = tfdb->uppart) {
				if (tfdb->partdepth <= i)
					break;
			}
			printw("%d", tfdb->partnum + 1);
		}
		addstr(" (");
		while ((fdb->flags & FDB_INLINE) && fdb->nextpart != NULL) {
			addstr("Inline: ");	/* multiple Inline: is OK */
			fdb = fdb->nextpart;
		}
		if (fdb->type.len) {
			i = fdb->type.len;
			if (i > 36)
				i = 35;
			printw("%.*s", i, fdb->type.ptr);
			if (i != fdb->type.len)
				addstr("..");
		}
		switch (fdb->flags & FDB_CHARSET) {
		case FDB_CS_SJIS:
			addstr(":SJIS");
			break;
		case FDB_CS_UTF8:
			addstr(":UTF8");
			break;
		case FDB_CS_GBK:
			addstr(":GBK");
			break;
		}
		addch(')');
		hline(A_REVERSE|'-', COLS);
	}

	if (state->hlpwin.lines) {
		move(state->hlpwin.y + state->hlpwin.lines, 0);
		printw("--%c%%-Cue: %s", nomime, state->helptitle);
		hline(A_REVERSE|'-', COLS);
	}

	if (state->config.disptime)
		disp_time(state);
	attroff(A_REVERSE);
	move(LINES-1, 0);
	if (state->status[0])
		addstr(state->status);
	clrtoeol();

	fdb = NULL;
	if (state->msgwin.lines) {
		fdb = state->message;
		if (fdb->msgnum != fl->msg[pos].num)
			fdb = NULL;
	}

  again:
	for (i = 0, npart = 0; i + npart < win->lines; i++) {
		if (cangetch(0))
			break;
		buf = folder_read(state, win->off + i);
		move(i + npart, 0);
		if (buf)
			addnstr(buf->ptr, buf->len);
		clrtoeol();
		if (fdb
		&&  win->off + i == pos
		&&  (fdb->flags & (FDB_MULTIPART|FDB_SUBPART))) {
			npart = disp_multipart(state, &cursy, 0);
			if (i + npart >= win->lines) {
				if (npart >= win->lines) {
					cursy = 0;
					disp_multipart(state, &cursy, npart - win->lines + 1);
					break;
				} else {
					win->off += i + npart + 1 - win->lines;
					cursy = pos - win->off;
				}
				goto again;
			}
			if (fdb->nextpart == NULL)
				lastpart = 1;
		}
	}

	win = &state->msgwin;
	if (win->lines) {
		fdb = state->message;
		for (i = 0, last = 0; i < win->lines; i++) {
			if (cangetch(0))
				break;
			buf = fdb_read(fdb, win->off + i);
			move(win->y + i, 0);
			if (buf) {
				addnstr(buf->ptr, buf->len);
				last = win->off + i;
			}
			clrtoeol();
			if (i > 0)
				contline(win->y + i,
				    fdb_ismore(fdb, win->off + i - 1));
		}
		if (lastpart && fdb->nextpart) {
			lastpart = 0;
			win = &state->folwin;
			cursy = pos - win->off;
			goto again;
		}

		/* show ##% in status line */
		more = disp_getmore(fdb, last);
		move(win->y + win->lines, COLS - 14);
		attron(A_REVERSE);
		if (more >= 100)
			addstr(fdb->nextpart ? "EOP" : "END");
		else
			printw("%d%%", more);
		attroff(A_REVERSE);
	}
	if (state->hlpwin.lines) {
		win = &state->hlpwin;
		fdb = state->help;
		for (i = 0; i < win->lines; i++) {
			if (cangetch(0))
				break;
			buf = fdb_read(fdb, win->off + i);
			move(win->y + i, 0);
			if (buf)
				addnstr(buf->ptr, buf->len);
			clrtoeol();
		}
		while (!(fdb->flags & FDB_EOF))
			(void)fdb_read(fdb, fdb->lines);
		if (fdb->lines)
			more = (win->off + win->lines) * 100 / fdb->lines;
		else
			more = 100;
		move(win->y + win->lines, COLS - 14);
		attron(A_REVERSE);
		if (more >= 100)
			addstr("END");
		else
			printw("%d%%", more);
		attroff(A_REVERSE);
		cursy = win->y;
	}
	if (state->mlen) {
		if (state->myoff >= 0)
			cursy = state->myoff - win->off + win->y;
		move(cursy, state->mxoff);
		innstr(matchbuf, state->mlen);
		attron(A_BOLD);
		addnstr(matchbuf, state->mlen);
		attroff(A_BOLD);
		move(cursy, state->mxoff);
	} else
		move(cursy, 0);
	refresh();
}

void
disp_time(struct state *state)
{
	int cursx, cursy;
	time_t now;
	struct tm *tm;
	int *yp, ys[4];
	char buf[10];

	time(&now);
	tm = localtime(&now);
	getyx(stdscr, cursy, cursx);
	yp = ys;
	*yp++ = state->folwin.y + state->folwin.lines;
	if (state->msgwin.lines)
		*yp++ = state->msgwin.y + state->msgwin.lines;
	if (state->hlpwin.lines)
		*yp++ = state->hlpwin.y + state->hlpwin.lines;
	*yp = -1;
	for (yp = ys; *yp >= 0; yp++) {
		move(*yp, COLS - 8);
		instr(buf);
		if (strcmp(buf, "--------") == 0
		||  (buf[0] == '[' && buf[3] == ':' && buf[6] == ']')) {
			attron(A_REVERSE);
			printw("[%02d:%02d]-", tm->tm_hour, tm->tm_min);
			attroff(A_REVERSE);
		} else
			strlcpy(state->status, buf, sizeof(state->status));
	}
	move(cursy, cursx);
}

/*ARGSUSED*/
void
disp_redraw(struct state *state)
{
	clearok(stdscr, TRUE);
	refresh();
	clearok(stdscr, FALSE);
}

void
disp_center(struct state *state)
{
	struct window *win = &state->folwin;

	win->off = state->folder->pos - (win->lines - 1) / 2;
	if (win->off < 0)
		win->off = 0;
}

void
disp_setlines(struct state *state)
{
	char *ep = NULL;
	int lines, linesall;
	char buf[CHARBLOCK];

	strlcpy(state->status, "lines: ", sizeof(state->status));
	buf[0] = '\0';
	if (edit_stline(state, buf, sizeof(buf), NULL) == NULL) {
		strlcpy(state->status, "Quit", sizeof(state->status));
		return;
	}
	lines = strtol(buf, &ep, 10);
	if (*ep == '/')
		linesall = strtol(ep + 1, &ep, 10);
	else
		linesall = 0;
	if (*ep != '\0' || lines <= 0) {
		strlcpy(state->status, "invalid value", sizeof(state->status));
		return;
	}
	state->status[0] = '\0';
	state->config.lines = lines;
	state->config.linesall = linesall;
	if (state->msgmode) {
		disp_msgmode(state, 1);
		message_open(state, 0);
	} else {
		disp_msgmode(state, 0);
	}
}

void
disp_help(struct state *state)
{
	int ch;
	struct filedb *fdb;
	struct window *win;
	ccbuf_t cbuf;
	extern char cuehlp[];

	if ((fdb = state->help) == NULL) {
		cbuf.ptr = cuehlp;
		cbuf.len = strlen(cuehlp);
		state->help = fdb = fdb_mkpart(&cbuf);
		if (fdb == NULL) {
			snprintf(state->status, sizeof(state->status),
			    "cue.hlp: %s", strerror(errno));
			return;
		}
		state->helptitle = "** Help **";
	}
	win = &state->hlpwin;
	win->off = 0;
  again:
	snprintf(state->status, sizeof(state->status),
	    "Type Enter to return from %s", state->helptitle);
	for (;;) {
		disp_update(state);
		cangetch(-1);
		ch = getch();
		switch (ch) {
		case '\n':
		case '\r':
		case CTRL('g'):
		case 'q':
			goto end;
		case CTRL('l'):
			clearok(stdscr, TRUE);
			break;
		case CTRL('e'):
			if (fdb_read(fdb, win->off + win->lines) == NULL) {
				beep();
				break;
			}
			insdelln(-1);
			win->off++;
			break;
		case CTRL('y'):
			if (win->off == 0) {
				beep();
				break;
			}
			win->off--;
			insdelln(1);
			break;
		case CTRL('f'):
		case ' ':
			if (fdb_read(fdb, win->off + win->lines) == NULL) {
				beep();
				break;
			}
			win->off += win->lines;
			break;
		case CTRL('b'):
		case '\b':
			if (win->off == 0) {
				beep();
				break;
			}
			if (win->off > win->lines)
				win->off -= win->lines;
			else
				win->off = 0;
			break;
		case '[':
			win->off = 0;
			break;
		case ']':
			while (!(fdb->flags & FDB_EOF))
				(void)fdb_read(fdb, fdb->lines);
			if (fdb->lines > win->lines)
				win->off = fdb->lines - win->lines;
			else
				win->off = 0;
			break;
		case '/':
			isearch(state, fdb, 1);
			goto again;
		case '?':
			isearch(state, fdb, -1);
			goto again;
		default:
			beep();
			break;
		}
	}
  end:
	state->help = NULL;
	state->status[0] = '\0';
}

/*
 * get the percentage of message displayed in window against
 * total message size.
 */
int
disp_getmore(struct filedb *fdb, int last)
{
	int more, lines;
	struct filedb *tfdb;

	lines = fdb->lines;
	if (fdb->flags & FDB_INLINE) {
		fdb = fdb->nextpart;
		while (fdb->flags & FDB_HIDDEN) {
			fdb = fdb->nextpart;
		}
		last += fdb->skip_lines;	/*XXX*/
		lines += fdb->lines;
	}
	more = lines ? (last + 1) * 100 / lines : 100;
	if (!(fdb->flags & FDB_EOF)) {
		/* this parse is not completed yet. try to guess */
		for (tfdb = fdb; tfdb->uppart; tfdb = tfdb->uppart)
			;
		more = more * (fdb->ptr - tfdb->mmap.ptr) / tfdb->mmap.len;
	}

	return more;
}
