/* $Id$ */
#include "bbs.h"
#define MORE_BUFSIZE	4096
#define MORE_WINSIZE	4096
#define STR_ANSICODE    "[0123456789;,"

static int      more_base, more_size, more_head;
static unsigned char more_pool[MORE_BUFSIZE];


static char    *more_help[] = {
    "\0閱\讀文章功\能鍵使用說明",
    "\01游標移動功\能鍵",
    "(↑)                  上捲一行",
    "(↓)(Enter)           下捲一行",
    "(^B)(PgUp)(BackSpace) 上捲一頁",
    "(→)(PgDn)(Space)     下捲一頁",
    "(0)(g)(Home)          檔案開頭",
    "($)(G) (End)          檔案結尾",
    "\01其他功\能鍵",
    "(/)                   搜尋字串",
    "(n/N)                 重複正/反向搜尋",
    "(TAB)                 URL連結",
    "(Ctrl-T)              存到暫存檔",
    "(:/f/b)               跳至某頁/下/上篇",
    "(a/A)                 跳至同一作者下/上篇",
    "([/])                 主題式閱\讀 上/下",
    "(t)                   主題式循序閱\讀",
    "(q)(←)               結束",
    "(h)(H)(?)             輔助說明畫面",
    NULL
};

int             beep = 0;

static void
more_goto(int fd, off_t off)
{
    int             base = more_base;

    if (off < base || off >= base + more_size) {
	more_base = base = off & (-MORE_WINSIZE);
	lseek(fd, base, SEEK_SET);
	more_size = read(fd, more_pool, MORE_BUFSIZE);
    }
    more_head = off - base;
}

static int
more_readln(int fd, unsigned char *buf)
{
    int             ch;

    unsigned char  *data, *tail, *cc;
    int             len, bytes, in_ansi, in_big5;
    int             size, head, ansilen;

    len = bytes = in_ansi = in_big5 = ansilen = 0;
    tail = buf + ANSILINELEN - 1;
    size = more_size;
    head = more_head;
    data = &more_pool[head];

    do {
	if (head >= size) {
	    more_base += size;
	    data = more_pool;
	    more_size = size = read(fd, data, MORE_BUFSIZE);
	    if (size == 0)
		break;
	    head = 0;
	}
	ch = *data++;
	head++;
	bytes++;
	if (ch == '\n') {
	    break;
	}
	if (ch == '\t') {
	    do {
		*buf++ = ' ';
	    }
	    while ((++len & 7) && len < t_columns);
	} else if (ch == '\033') {
	    if (atoi((char *)(data + 1)) > 47) {
		if ((cc = (unsigned char *)strchr((char *)(data + 1), 'm')) != NULL) {
		    ch = cc - data + 1;

		    data += ch;
		    head += ch;
		    bytes += ch;
		}
	    } else {
		if (showansi)
		    *buf++ = ch;
		in_ansi = 1;
	    }
	} else if (in_ansi) {
	    if (showansi)
		*buf++ = ch;
	    if (!strchr(STR_ANSICODE, ch))
		in_ansi = 0;
	} else if (in_big5) {
	    ++len;
	    *buf++ = ch;
	    in_big5 = 0;
	} else if (isprint2(ch)) {
	    len++;
	    *buf++ = ch;
	    if (ch >= 0xa1 && ch <= 0xf9) /* first byte of big5 encoding */
		in_big5 = 1;
	}
    } while (len < t_columns && buf < tail);

    if (in_big5 && len >= t_columns) {
	--buf;
	--head;
	--data;
	--bytes;
    }

    if (len == t_columns && head < size && *data == '\n') {
      /* XXX: not handle head==size, should read data */
      /* no extra newline dirty hack for exact 80byte line */
      data++; bytes++; head++;
    }
    *buf = '\0';
    more_head = head;
    return bytes;
}

int
more(char *fpath, int promptend)
{
    char    *head[4] = {"作者", "標題", "時間", "轉信"};
    char           *ptr, *word = NULL, buf[ANSILINELEN + 1];
    struct stat     st;

    int             fd, fsize;

    unsigned int    pagebreak[MAX_PAGES], pageno, lino = 0;
    int             line, ch, viewed, pos, numbytes;
    int             header = 0;
    int             local = 0;
    char            search_char0 = 0;
    static char     search_str[81] = "";
    typedef char   *(*FPTR) ();
    static FPTR     fptr;
    int             searching = 0;
    int             scrollup = 0;
    char           *printcolor[3] = {"44", "33;45", "0;34;46"}, color = 0;
    /* Ptt */

    memset(pagebreak, 0, sizeof(pagebreak));
    if (*search_str)
	search_char0 = *search_str;
    *search_str = 0;

    fd = open(fpath, O_RDONLY, 0600);
    if (fd < 0)
	return -1;

    if (fstat(fd, &st) || ((fsize = st.st_size) <= 0) || S_ISDIR(st.st_mode)) {
	close(fd);
	//Ptt
	    return -1;
    }
    pagebreak[0] = pageno = viewed = line = pos = 0;
    clear();

    /* rocker */

    more_base = more_head = more_size = 0;

    while ((numbytes = more_readln(fd, (unsigned char *)buf)) || (line == t_lines)) {
	if (scrollup) {
	    rscroll();
	    move(0, 0);
	}
	if (numbytes) {		/* 一般資料處理 */
	    if (!viewed) {	/* begin of file */
		if (showansi) {	/* header processing */
		    if (!strncmp(buf, str_author1, LEN_AUTHOR1)) {
			line = 3;
			word = buf + LEN_AUTHOR1;
			local = 1;
		    } else if (!strncmp(buf, str_author2, LEN_AUTHOR2)) {
			line = 4;
			word = buf + LEN_AUTHOR2;
		    }
		    while (pos < line) {
			if (!pos && ((ptr = strstr(word, str_post1)) ||
				     (ptr = strstr(word, str_post2)))) {
			    ptr[-1] = '\0';
			    prints("\033[47;34m %s \033[44;37m%-53.53s"
				"\033[47;34m %.4s \033[44;37m%-13s\033[m\n",
				   head[0], word, ptr, ptr + 5);
			} else if (pos < line)
			    prints("\033[47;34m %s \033[44;37m%-72.72s"
				   "\033[m\n", head[pos], word);

			viewed += numbytes;
			numbytes = more_readln(fd, (unsigned char *)buf);

			/* 第一行太長了 */
			if (!pos && viewed > 79) {
			    /* 第二行不是 [標....] */
			    if (memcmp(buf, head[1], 2)) {
				/* 讀下一行進來處理 */
				viewed += numbytes;
				numbytes = more_readln(fd, (unsigned char *)buf);
			    }
			}
			pos++;
		    }
		    if (pos) {
			header = 1;

			prints("\033[36m%s\033[m\n", msg_seperator);
			++line;
			++pos;
		    }
		}
		lino = pos;
		word = NULL;
	    }
	    /* ※處理引用者 & 引言 */
	    if ((buf[1] == ' ') && (buf[0] == ':' || buf[0] == '>'))
		word = "\033[36m";
	    else if (!strncmp(buf, "※", 2) || !strncmp(buf, "==>", 3))
		word = "\033[32m";

	    if (word)
		outs(word);
	    {
		char            msg[500], *pos;

		if (*search_str && (pos = fptr(buf, search_str))) {
		    char            SearchStr[81];
		    char            buf1[100], *pos1;

		    strncpy(SearchStr, pos, strlen(search_str));
		    SearchStr[strlen(search_str)] = 0;
		    searching = 0;
		    snprintf(msg, sizeof(msg),
			     "%.*s\033[7m%s\033[m", pos - buf, buf,
			     SearchStr);
		    while ((pos = fptr(pos1 = pos + strlen(search_str),
				       search_str))) {
			snprintf(buf1, sizeof(buf1),
				 "%.*s\033[7m%s\033[m", pos - pos1,
				 pos1, SearchStr);
			strlcat(msg, buf1, sizeof(msg));
		    }
		    strlcat(msg, pos1, sizeof(msg));
		    outs(Ptt_prints(msg, NO_RELOAD));
		} else
		    outs(Ptt_prints(buf, NO_RELOAD));
	    }
	    if (word) {
		outs("\033[m");
		word = NULL;
	    }
	    outch('\n');

	    if (beep) {
		bell();
		beep = 0;
	    }
	    if (line < b_lines)	/* 一般資料讀取 */
		line++;

	    if (line == b_lines && searching == -1) {
		if (pageno > 0)
		    more_goto(fd, viewed = pagebreak[--pageno]);
		else
		    searching = 0;
		lino = pos = line = 0;
		clear();
		continue;
	    }
	    if (scrollup) {
		move(line = b_lines, 0);
		clrtoeol();
		for (pos = 1; pos < b_lines; pos++)
		    viewed += more_readln(fd, (unsigned char *)buf);
	    } else if (pos == b_lines)	/* 捲動螢幕 */
		scroll();
	    else
		pos++;

	    if (!scrollup                   &&
		++lino >= (unsigned)b_lines &&
		pageno < MAX_PAGES - 1         ) {
		pagebreak[++pageno] = viewed;
		lino = 1;
	    }
	    if (scrollup) {
		lino = scrollup;
		scrollup = 0;
	    }
	    viewed += numbytes;	/* 累計讀過資料 */
	} else
	    line = b_lines;	/* end of END */

	if (promptend &&
	    ((!searching && line == b_lines) || viewed == fsize)) {
	    /* Kaede 剛好 100% 時不停 */
	    move(b_lines, 0);
	    if (viewed == fsize) {
		if (searching == 1)
		    searching = 0;
		color = 0;
	    } else if (pageno == 1 && lino == 1) {
		if (searching == -1)
		    searching = 0;
		color = 1;
	    } else
		color = 2;

	    prints("\033[m\033[%sm  瀏覽 P.%d(%d%%)  %s  %-30.30s%s",
		   printcolor[(int)color],
		   pageno,
		   (int)((viewed * 100) / fsize),
		   "\033[31;47m",
		   "(h)\033[30m求助  \033[31m→↓[PgUp][",
		   "PgDn][Home][End]\033[30m游標移動  \033[31m←[q]\033[30m結束   \033[m");


	    while (line == b_lines || (line > 0 && viewed == fsize)) {
		switch ((ch = igetch())) {
		case ':':
		    {
			char            buf[10];
			int             i = 0;

			getdata(b_lines - 1, 0, "Goto Page: ", buf, 5, DOECHO);
			sscanf(buf, "%d", &i);
			if (0 < i && i < MAX_PAGES && (i == 1 || pagebreak[i - 1]))
			    pageno = i - 1;
			else if (pageno)
			    pageno--;
			lino = line = 0;
			break;
		    }
		case '/':
		    {
			char            ans[4] = "n";

			*search_str = search_char0;
			getdata_buf(b_lines - 1, 0, "[搜尋]關鍵字:", search_str,
				    40, DOECHO);
			if (*search_str) {
			    searching = 1;
			    if (getdata(b_lines - 1, 0, "區分大小寫(Y/N/Q)? [N] ",
				   ans, sizeof(ans), LCECHO) && *ans == 'y')
				fptr = strstr;
			    else
				fptr = strcasestr;
			}
			if (*ans == 'q')
			    searching = 0;
			if (pageno)
			    pageno--;
			lino = line = 0;
			break;
		    }
		case 'n':
		    if (*search_str) {
			searching = 1;
			if (pageno)
			    pageno--;
			lino = line = 0;
		    }
		    break;
		case 'N':
		    if (*search_str) {
			searching = -1;
			if (pageno)
			    pageno--;
			lino = line = 0;
		    }
		    break;
		case 'r': // Ptt: put all reply/recommend function here
		case 'R':
		case 'Y':
		case 'y':
		    close(fd);
		    return 999;
		case 'X':
		    close(fd);
		    return 998;
		case 'A':
		    close(fd);
		    return AUTHOR_PREV;
		case 'a':
		    close(fd);
		    return AUTHOR_NEXT;
		case 'F':
		case 'f':
		    close(fd);
		    return READ_NEXT;
		case 'B':
		case 'b':
		    close(fd);
		    return READ_PREV;
		case KEY_LEFT:
		case 'q':
		    close(fd);
		    return FULLUPDATE;
		case ']':	/* Kaede 為了主題閱讀方便 */
		    close(fd);
		    return RELATE_NEXT;
		case '[':	/* Kaede 為了主題閱讀方便 */
		    close(fd);
		    return RELATE_PREV;
		case '=':	/* Kaede 為了主題閱讀方便 */
		    close(fd);
		    return RELATE_FIRST;
		case Ctrl('F'):
		case KEY_PGDN:
		    line = 1;
		    break;
		case 't':
		    if (viewed == fsize) {
			close(fd);
			return RELATE_NEXT;
		    }
		    line = 1;
		    break;
		case ' ':
		    if (viewed == fsize) {
			close(fd);
			return READ_NEXT;
		    }
		    line = 1;
		    break;
		case KEY_RIGHT:
		    if (viewed == fsize) {
			close(fd);
			return 0;
		    }
		    line = 1;
		    break;
		case '\r':
		case '\n':
		case KEY_DOWN:
		    if (viewed == fsize ||
			(promptend == 2 && (ch == '\r' || ch == '\n'))) {
			close(fd);
			return READ_NEXT;
		    }
		    line = t_lines - 2;
		    break;
		case '$':
		case 'G':
		case KEY_END:
		    line = t_lines;
		    break;
		case '0':
		case 'g':
		case KEY_HOME:
		    pageno = line = 0;
		    break;
		case 'h':
		case 'H':
		case '?':
		    /* Kaede Buggy ... */
		    show_help(more_help);
		    if (pageno)
			pageno--;
		    lino = line = 0;
		    break;
		case 'E':
		    if (HAS_PERM(PERM_SYSOP) && strcmp(fpath, "etc/ve.hlp")) {
			close(fd);
			vedit(fpath, NA, NULL);
			return 0;
		    }
		    break;

		case Ctrl('T'):
		    getdata(b_lines - 2, 0, "把這篇文章收入到暫存檔？[y/N] ",
			    buf, 4, LCECHO);
		    if (buf[0] == 'y') {
			setuserfile(buf, ask_tmpbuf(b_lines - 1));
                        Copy(fpath, buf);
		    }
		    if (pageno)
			pageno--;
		    lino = line = 0;
		    break;
		case KEY_UP:
		    line = -1;
		    break;
		case Ctrl('B'):
		case KEY_PGUP:
		    if (pageno > 1) {
			if (lino < 2)
			    pageno -= 2;
			else
			    pageno--;
			lino = line = 0;
		    } else if (pageno && lino > 1)
			pageno = line = 0;
		    break;
		case Ctrl('H'):
		    if (pageno > 1) {
			if (lino < 2)
			    pageno -= 2;
			else
			    pageno--;
			lino = line = 0;
		    } else if (pageno && lino > 1)
			pageno = line = 0;
		    else {
			close(fd);
			return READ_PREV;
		    }
		}
	    }

	    if (line > 0) {
		move(b_lines, 0);
		clrtoeol();
		refresh();
	    } else if (line < 0) {	/* Line scroll up */
		if (pageno <= 1) {
		    if (lino == 1 || !pageno) {
			close(fd);
			return READ_PREV;
		    }
		    if (header && lino <= 5) {
			more_goto(fd, viewed = pagebreak[scrollup = lino =
							 pageno = 0] = 0);
			clear();
		    }
		}
		if (pageno && (int)lino > 1 + local) {
		    line = (lino - 2) - local;
		    if (pageno > 1 && viewed == fsize)
			line += local;
		    scrollup = lino - 1;
		    more_goto(fd, viewed = pagebreak[pageno - 1]);
		    while (line--)
			viewed += more_readln(fd, (unsigned char *)buf);
		} else if (pageno > 1) {
		    scrollup = b_lines - 1;
		    line = (b_lines - 2) - local;
		    more_goto(fd, viewed = pagebreak[--pageno - 1]);
		    while (line--)
			viewed += more_readln(fd, (unsigned char *)buf);
		}
		line = pos = 0;
	    } else {
		pos = 0;
		more_goto(fd, viewed = pagebreak[pageno]);
		move(0, 0);
		clear();
	    }
	}
    }

    close(fd);
    if (promptend) {
	pressanykey();
	clear();
    } else
	outs(reset_color);
    return 0;
}
