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

/*-
 * Copyright (c) 1998-1999 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 <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef USE_CANNA
#include <canna/jrkanji.h>
#endif /* USE_CANNA */

#include "cue.h"

#ifdef USE_CANNA
static char *cannamode;
static int cannamodelen = -1;
#endif /* USE_CANNA */

struct edit_state {
	struct state *state;
	char	*buf;
	int	size;
	int	len;
	int	pos;
	int	nmatch;
	int	(*completion)(char *candidate, void (*callback)(void *arg, const char *entry), void *arg);
	struct	kbuf *kbuf;
	int	compmaxlen;
	char	**complist;
	int	complistent;
	int	complistsize;
	struct	abuf abuf;
#ifdef USE_CANNA
	int	canna;
	jrKanjiStatus ks;
	char	cannabuf[CHARBLOCK];
#endif /* USE_CANNA */
};

static void
edit_update(struct edit_state *e)
{
	char *p;
	int maxpos, msglen, off, width;
	int y, x;

	switch (e->nmatch) {
	case 0:
	case 1:
		if (e->nmatch == 0)
			p = " [No match]";
		else
			p = " [Sole completion]";
		e->pos = e->len;
		maxpos = e->len + strlen(p);
		break;
	default:
		p = NULL;
		if (e->pos == e->len)
			maxpos = e->len;
		else
			maxpos = e->pos + 1;
		break;
	}
	move(LINES - 1, 0);
#ifdef USE_CANNA_XXX
	if (e->canna)
		addstr(cannamode);
#endif /* USE_CANNA */
	msglen = strlen(e->state->status);
	getyx(stdscr, y, x);
	width = COLS - x - 1;
	off = maxpos - width;
	if (off < -msglen)
		off = -msglen;
	else {
		/* prepend '+' */
		addch('+');
		width--;
		off++;
		x++;
	}
	if (e->len > width + off) {
		if (off > -msglen)
			off++;
		if (e->len > width + off) {
			/* append '+' */
			width--;
		}
	}

	if (off < 0) {
		addnstr(e->state->status + msglen + off, -off);
		addnstr(e->buf, width + off);
	} else
		addnstr(e->buf + off, width);
	if (e->len - off > width)
		addch('+');
	if (p)
		addstr(p);
	clrtoeol();
	x += e->pos - off;
	move(y, x);
#ifdef USE_CANNA
	if (e->canna && e->ks.length) {
		/* XXX: e->ks.length -> width (hankana) */
		if (x + (2 + e->ks.length) > COLS) {
			x = COLS - (2 + e->ks.length);
			if (x < 0)
				x = 0;	/*XXX*/
			move(y, x);
		}
		addch('|');
		p = (char *)e->ks.echoStr;
		if (e->ks.revPos) {
			addnstr(p, e->ks.revPos);
			p += e->ks.revPos;
		}
		if (e->ks.revLen) {
			attron(A_REVERSE);
			addnstr(p, e->ks.revLen);
			attroff(A_REVERSE);
			p += e->ks.revLen;
		}
		if (e->ks.revPos + e->ks.revLen < e->ks.length)
			addnstr(p, e->ks.length - (e->ks.revPos + e->ks.revLen));
		addstr("|\b");
	}
#endif /* USE_CANNA */
}

static void
completion_add(void *arg, const char *str)
{
	struct edit_state *e = arg;
	int len;
	char *p;
	char **q;

	len = strlen(str);
	if (e->compmaxlen < len)
		e->compmaxlen = len;
	len++;	/* XXX: include '\0' */
	p = copy_kbuf(&e->kbuf, str, len);
	if (e->complistent >= e->complistsize) {
		q = realloc(e->complist,
		    sizeof(char *) * (e->complistsize + LINEBLOCK));
		if (!q)
			abort();
		e->complist = q;
		e->complistsize += LINEBLOCK;
	}
	e->complist[e->complistent++] = p;
}

static int
completion_cmp(const void *p1, const void *p2)
{
	char **s1 = (char **)p1, **s2 = (char **)p2;

	return strcmp(*s1, *s2);
}

static void
completion_show(struct edit_state *e)
{
	struct kbuf *kbuf;
	char *p, *q;
	char buf[LINE_WIDTH + 1];
	int i, j, ptr, len, width, cols, lines;

	e->complistent = 0;
	e->compmaxlen = 1;
	for (kbuf = e->kbuf; kbuf; kbuf = kbuf->next)
		kbuf->inuse = 0;
	(void)(*e->completion)(e->buf, completion_add, e);
	qsort(e->complist, e->complistent, sizeof(char *), completion_cmp);
	width = e->compmaxlen + 2;
	cols = LINE_WIDTH / width;
	if (cols == 0)
		cols = 1;
	lines = (e->complistent + cols - 1) / cols;
	len = 0;
	ptr = 0;
	e->abuf.buf.len = 0;
	for (i = 0; i < lines; i++) {
		q = buf;
		for (j = 0, len = LINE_WIDTH; j < cols; j++) {
			if (i + j * lines >= e->complistent)
				break;
			p = e->complist[i + j * lines];
			for (; len < width; len++)
				*q++ = ' ';
			len = strlen(p);
			memcpy(q, p, len);
			q += len;
		}
		*q++ = '\n';
		copy_abuf(&e->abuf, buf, q - buf);
	}
	e->state->help = fdb_mkpart((ccbuf_t *)&e->abuf.buf);
	e->state->helptitle = "** Completion **";
	e->state->hlpwin.off = 0;
	disp_update(e->state);
}

static void
completion_scroll(struct edit_state *e)
{
	struct window *win;

	win = &e->state->hlpwin;
	if (fdb_read(e->state->help, win->off + win->lines) == NULL) {
		beep();
		return;
	}
	win->off += win->lines;
	disp_update(e->state);
}

static void
completion_free(struct edit_state *e)
{
	struct kbuf *kbuf, *nkbuf;

	for (kbuf = e->kbuf; kbuf; kbuf = nkbuf) {
		nkbuf = kbuf->next;
		free(kbuf);
	}
	if (e->complist)
		free(e->complist);
	if (e->state->help) {
		fdb_clear(e->state->help);
		e->state->help = NULL;
	}
	if (e->abuf.buf.ptr)
		free(e->abuf.buf.ptr);
}

static int
edit_control(struct edit_state *e, int ch)
{
	int i;
#ifdef USE_CANNA
	jrKanjiStatusWithValue ksv;
#endif /* USE_CANNA */

	switch (ch) {
	/* refresh */
	case CTRL('l'):
		clearok(stdscr, TRUE);
		break;

	/* cursor */
	case CTRL('a'):
		e->pos = 0;
		break;
	case CTRL('e'):
		e->pos = e->len;
		break;
	case CTRL('f'):
		if (e->pos == e->len) {
			beep();
			break;
		}
		if (e->buf[e->pos++] & 0x80)
			e->pos++;
		break;
	case CTRL('b'):
		if (e->pos == 0) {
			beep();
			break;
		}
		if (e->buf[--e->pos] & 0x80)
			--e->pos;
		break;

	/* kill */
	case CTRL('u'):
		e->pos = 0;
		/*FALLTHRU*/
	case CTRL('k'):
		e->len = e->pos;
		e->buf[e->len] = '\0';
		break;
	case CTRL('h'):
		if (e->pos == 0) {
			beep();
			break;
		}
		if (e->buf[--e->pos] & 0x80)
			--e->pos;
		/*FALLTHRU*/
	case CTRL('d'):
	case 0177:	/* DEL */
		if (e->pos == e->len) {
			beep();
			break;
		}
		i = (e->buf[e->pos] & 0x80) ? 2 : 1;
		e->len -= i;
		memmove(e->buf + e->pos, e->buf + e->pos + i, e->len - e->pos);
		e->buf[e->len] = '\0';
		break;
	case CTRL('w'):
		for (i = e->pos; i; ) {
			if (--i == 0)
				break;
			if (e->buf[i - 1] == ' ')
				break;
		}
		if (i < e->pos) {
			if (e->pos < e->len)
				memmove(e->buf + i, e->buf + e->pos, e->len - e->pos);
			e->len -= e->pos - i;
			e->buf[e->len] = '\0';
			e->pos = i;
		}
		break;
	case '\t':	/* completion */
		if (e->state->help) {
			completion_scroll(e);
			break;
		}
		e->nmatch = (*e->completion)(e->buf, NULL, NULL);
		e->pos = strlen(e->buf);
		if (e->nmatch > 1 && e->pos == e->len) {
			completion_show(e);
		}
		e->len = e->pos;
		break;
#ifdef USE_CANNA
	case CTRL('o'):
		if (e->canna) {
			e->canna = 0;
			/* TODO: get kakutei ? */
			break;
		}
		if (cannamode == NULL) {
			jrKanjiControl(0, KC_INITIALIZE, NULL);
			i = jrKanjiControl(0, KC_QUERYMAXMODESTR, 0);
			cannamode = malloc(cannamodelen = i + 1);
			if (cannamode == NULL)
				break;
		}
		e->canna = 1;
		jrKanjiControl(0, KC_INITIALIZE, 0);
		e->ks.length = 0;
		ksv.ks = &e->ks;
		ksv.buffer = (u_char *)e->cannabuf;
		ksv.bytes_buffer = sizeof(e->cannabuf);
		ksv.val = CANNA_MODE_HenkanMode;
		jrKanjiControl(0, KC_CHANGEMODE, (char *)&ksv);
		jrKanjiControl(0, KC_SETMODEINFOSTYLE, 0);
		jrKanjiControl(0, KC_QUERYMODE, cannamode);
		break;
#endif /* USE_CANNA */
	default:
		return 0;
	}
	return 1;
}

char *
edit_stline(struct state *state, char *buf, int size, int (*completion)(char *candidate, void (*callback)(void *arg, const char *entry), void *arg))
{
	struct edit_state ebuf, *e = &ebuf;
	int i;
	int ch;
	u_char *inp;
	u_char inbuf[3];

	memset(e, 0, sizeof(*e));
	e->state = state;
	e->buf = buf;
	e->size = size;
	e->completion = completion;
	e->len = strlen(buf);
	e->pos = e->len;
	e->nmatch = -1;

	move(LINES - 1, 0);

	for (;;) {
		edit_update(e);
		refresh();
		if (e->nmatch == 0 || e->nmatch == 1) {
			e->nmatch = -1;
			clrtoeol();
			if (!cangetch(DEF_DELAY))
				refresh();
		}
		cangetch(-1);
		ch = getch();
		if (e->state->help && (ch != ' ' && ch != '\t')) {
			fdb_clear(e->state->help);
			e->state->help = NULL;
			disp_update(e->state);
		}
		inp = inbuf;
		inp[0] = ch;
		inp[1] = '\0';
#ifdef USE_CANNA
		if (e->canna) {
			i = jrKanjiString(0, ch, e->cannabuf, e->size - e->len,
					  &e->ks);
			if (e->ks.info & KanjiModeInfo) {
				jrKanjiControl(0, KC_SETMODEINFOSTYLE, (char *)1);
				jrKanjiControl(0, KC_QUERYMODE, cannamode);
				jrKanjiControl(0, KC_SETMODEINFOSTYLE, (char *)0);
				if (*cannamode - '@' == CANNA_MODE_AlphaMode) {
					e->canna = 0;
					continue;
				}
				strlcpy(cannamode, (char *)e->ks.mode,
				    cannamodelen);
			}
			if (i == 0)
				continue;
			e->cannabuf[i] = '\0';
			inp = (u_char *)e->cannabuf;
		}
#endif /* USE_CANNA */
		while ((ch = *inp) != '\0') {
			switch (ch) {
			case CTRL('g'):
				buf = NULL;
				/*FALLTHRU*/
			case '\n':
			case '\r':
				if (e->completion)
					completion_free(e);
				return buf;
			case '\t':
			case '\033':
#if 0	/* disabled to allow space characters in file names */
			case ' ':
#endif
				ch = e->completion != NULL ? '\t' : ' ';
				break;
			}
			if (ch < ' ') {
				if (!edit_control(e, ch))
					beep();
				inp++;
				continue;
			}
			if (ch & 0x80) {
				i = 2;
				if (!inp[1]) {
					inp[2] = '\0';
					if (!cangetch(KANJI_TIMEOUT)
					||  ((inp[1] = getch()) & 0x80) == 0) {
						beep();
						inp++;
						continue;
					}
				}
			} else
				i = 1;
			if (e->len >= e->size - (i + 1)) {
				beep();
				inp++;
				continue;
			}
			if (e->pos < e->len)
				memmove(e->buf + e->pos + i, e->buf + e->pos,
					e->len - e->pos);
			memcpy(e->buf + e->pos, inp, i);
			inp += i;
			e->pos += i;
			e->len += i;
			e->buf[e->len] = '\0';
		}
	}
	/*NOTREACHED*/
}

int
edit_yn(struct state *state, const char *fmt, ...)
{
	int ch;
	va_list ap;

	move(LINES-1, 0);
	va_start(ap, fmt);
	vwprintw(stdscr, fmt, ap);
	va_end(ap);
	refresh();

	for (;;) {
		cangetch(-1);
		ch = getch();
		if (ch == 'y' || ch == 'Y' || ch == ' ') {
			ch = 'y';
			break;
		}
		if (ch == 'n' || ch == 'N' || ch == CTRL('g')) {
			ch = 'n';
			break;
		}
		beep();
	}
	addch(ch);
	refresh();
	return (ch == 'y');
}

int
read_passwd(const char *prompt, char *buf, int size)
{
	int c, len;

	move(LINES - 1, 0);
	if (prompt)
		addstr(prompt);
	else
		addstr("Password: ");
	clrtoeol();
	refresh();
	for (len = 0;;) {
		(void)cangetch(-1);
		switch (c = getch()) {
		case '\n':
		case '\r':
			goto end;
		case CTRL('g'):
		case ERR:
			len = 0;
			goto end;
		case CTRL('u'):
			len = 0;
			break;
		case '\b':
			if (len == 0)
				beep();
			else
				len--;
			break;
		default:
			if (len + 1 >= size)
				beep();
			else
				buf[len++] = c;
			break;
		}
	}
  end:
	move(LINES - 1, 0);
	clrtoeol();
	refresh();
	buf[len] = '\0';
	return strlen(buf);
}
