/*-
 * Copyright (c) 1993, 1994
 *	The Regents of the University of California.  All rights reserved.
 * Copyright (c) 1994, 1995
 *	Keith Bostic.  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. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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.
 */

#ifndef lint
static char sccsid[] = "@(#)cl_term.c	9.6 (Berkeley) 2/12/95";
#endif /* not lint */

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/queue.h>
#include <sys/time.h>

#include <bitstring.h>
#include <errno.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>

#include "compat.h"
#include <curses.h>
#include <db.h>
#include <regex.h>

#include "vi.h"
#include "cl.h"
#include "excmd.h"

/*
 * XXX
 * THIS REQUIRES THAT ALL SCREENS SHARE A TERMINAL TYPE.
 */
typedef struct _tklist {
	char	*ts;			/* Key's termcap string. */
	char	*output;		/* Corresponding vi command. */
	char	*name;			/* Name. */
	u_char	 value;			/* Special value (for lookup). */
} TKLIST;
static TKLIST const c_tklist[] = {	/* Command mappings. */
#ifdef SYSV_CURSES
	{"kil1",	"O",	"insert line"},
	{"kdch1",	"x",	"delete character"},
	{"kcud1",	"j",	"cursor down"},
	{"kel",		"D",	"delete to eol"},
	{"kind",     "\004",	"scroll down"},
	{"kll",		"$",	"go to eol"},
	{"khome",	"^",	"go to sol"},
	{"kich1",	"i",	"insert at cursor"},
	{"kdl1",       "dd",	"delete line"},
	{"kcub1",	"h",	"cursor left"},
	{"knp",	     "\006",	"page down"},
	{"kpp",	     "\002",	"page up"},
	{"kri",	     "\025",	"scroll up"},
	{"ked",	       "dG",	"delete to end of screen"},
	{"kcuf1",	"l",	"cursor right"},
	{"kcuu1",	"k",	"cursor up"},
#else
	{"kA",		"O",	"insert line"},
	{"kD",		"x",	"delete character"},
	{"kd",		"j",	"cursor down"},
	{"kE",		"D",	"delete to eol"},
	{"kF",	     "\004",	"scroll down"},
	{"kH",		"$",	"go to eol"},
	{"kh",		"^",	"go to sol"},
	{"kI",		"i",	"insert at cursor"},
	{"kL",	       "dd",	"delete line"},
	{"kl",		"h",	"cursor left"},
	{"kN",	     "\006",	"page down"},
	{"kP",	     "\002",	"page up"},
	{"kR",	     "\025",	"scroll up"},
	{"kS",	       "dG",	"delete to end of screen"},
	{"kr",		"l",	"cursor right"},
	{"ku",		"k",	"cursor up"},
#endif
	{NULL},
};
static TKLIST const m1_tklist[] = {	/* Input mappings (lookup). */
	{NULL},
};
static TKLIST const m2_tklist[] = {	/* Input mappings (set or delete). */
#ifdef SYSV_CURSES
	{"kcud1",  "\033ja",	"cursor down"},
	{"kcub1",  "\033ha",	"cursor left"},
	{"kcuu1",  "\033ka",	"cursor up"},
	{"kcuf1",  "\033la",	"cursor right"},
#else
	{"kd",	   "\033ja",	"cursor down"},
	{"kl",	   "\033ha",	"cursor left"},
	{"ku",	   "\033ka",	"cursor up"},
	{"kr",	   "\033la",	"cursor right"},
#endif
	{NULL},
};

/*
 * cl_term_init --
 *	Initialize the special keys defined by the termcap/terminfo entry.
 */
int
cl_term_init(sp)
	SCR *sp;
{
	KEYLIST *kp;
	SEQ *qp;
	TKLIST const *tkp;
	size_t len;
	char *sbp, *s, *t, sbuf[1024];

	/* Command mappings. */
	for (tkp = c_tklist; tkp->name != NULL; ++tkp) {
#ifdef SYSV_CURSES
		if ((t = tigetstr(tkp->ts)) == NULL || t == (char *)-1)
			continue;
#else
		sbp = sbuf;
		if ((t = tgetstr(tkp->ts, &sbp)) == NULL)
			continue;
#endif
		if (seq_set(sp, tkp->name, strlen(tkp->name), t, strlen(t),
		    tkp->output, strlen(tkp->output), SEQ_COMMAND,
		    SEQ_NOOVERWRITE | SEQ_SCREEN))
			return (1);
	}

	/* Input mappings needing to be looked up. */
	for (tkp = m1_tklist; tkp->name != NULL; ++tkp) {
#ifdef SYSV_CURSES
		if ((t = tigetstr(tkp->ts)) == NULL || t == (char *)-1)
			continue;
#else
		sbp = sbuf;
		if ((t = tgetstr(tkp->ts, &sbp)) == NULL)
			continue;
#endif
		for (kp = keylist;; ++kp)
			if (kp->value == tkp->value)
				break;
		if (kp == NULL)
			continue;
		if (seq_set(sp, tkp->name, strlen(tkp->name), t, strlen(t),
		    &kp->ch, 1, SEQ_INPUT, SEQ_NOOVERWRITE | SEQ_SCREEN))
			return (1);
	}

	/* Input mappings that are already set or are text deletions. */
	for (tkp = m2_tklist; tkp->name != NULL; ++tkp) {
#ifdef SYSV_CURSES
		if ((t = tigetstr(tkp->ts)) == NULL || t == (char *)-1)
			continue;
#else
		sbp = sbuf;
		if ((t = tgetstr(tkp->ts, &sbp)) == NULL)
			continue;
#endif
		/*
		 * !!!
		 * Some terminals' <cursor_left> keys send single <backspace>
		 * characters.  This is okay in command mapping, but not okay
		 * in input mapping.  That combination is the only one we'll
		 * ever see, hopefully, so kluge it here for now.
		 */
		if (!strcmp(t, "\b"))
			continue;
		if (tkp->output == NULL) {
			if (seq_set(sp, tkp->name, strlen(tkp->name),
			    t, strlen(t), NULL, 0,
			    SEQ_INPUT, SEQ_NOOVERWRITE | SEQ_SCREEN))
				return (1);
		} else
			if (seq_set(sp, tkp->name, strlen(tkp->name),
			    t, strlen(t), tkp->output, strlen(tkp->output),
			    SEQ_INPUT, SEQ_NOOVERWRITE | SEQ_SCREEN))
				return (1);
	}

	/*
	 * Rework any function key mappings that were set before the
	 * screen was initialized.
	 */
	for (qp = sp->gp->seqq.lh_first; qp != NULL; qp = qp->q.le_next)
		if (F_ISSET(qp, SEQ_FUNCMAP))
			(void)cl_fmap(sp, qp->stype,
			    qp->input, qp->ilen, qp->output, qp->olen);

	/* Set up the visual bell information. */
	t = sbuf;
	if (tgetstr("vb", &t) != NULL && (len = t - sbuf) != 0) {
		MALLOC_RET(sp, s, char *, len);
		memmove(s, sbuf, len);
		if (CLP(sp)->VB != NULL)
			free(CLP(sp)->VB);
		CLP(sp)->VB = s;
	}
	return (0);
}

/*
 * cl_term_end --
 *	End the special keys defined by the termcap/terminfo entry.
 */
int
cl_term_end(sp)
	SCR *sp;
{
	SEQ *qp, *nqp;

	/* Free visual bell information. */
	if (CLP(sp)->VB != NULL)
		free(CLP(sp)->VB);

	/* Delete screen specific mappings. */
	for (qp = sp->gp->seqq.lh_first; qp != NULL; qp = nqp) {
		nqp = qp->q.le_next;
		if (F_ISSET(qp, SEQ_SCREEN))
			(void)seq_mdel(qp);
	}
	return (0);
}

/*
 * cl_fmap --
 *	Map a function key.
 */
int
cl_fmap(sp, stype, from, flen, to, tlen)
	SCR *sp;
	enum seqtype stype;
	CHAR_T *from, *to;
	size_t flen, tlen;
{
	size_t nlen;
	int nf;
	char *p, *t, keyname[64];

	/* If the terminal isn't initialized, there's nothing to do. */
	if (!F_ISSET(CLP(sp), CL_CURSES_INIT))
		return (0);

#ifdef SYSV_CURSES
	(void)snprintf(keyname, sizeof(keyname), "kf%d", atoi(from + 1));
	if ((t = tigetstr(keyname)) == NULL || t == (char *)-1)
		t = NULL;
#else
	/*
	 * !!!
	 * Historically, the 4BSD termcap code didn't support functions keys
	 * greater than 9.  This was silently enforced -- asking for key k12
	 * returned the value for k1.  We try and get around this by using
	 * the tables specified in the terminfo(TI_ENV) man page from the 3rd
	 * Edition SVID.  This assumes that the implementors of any System V
	 * compatibility code or an extended termcap used those codes.
	 */
	{ int n; char *sbp, sbuf[1024];
	  static const char codes[] = {
/*  0-10 */	'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ';',
/* 11-19 */	'1', '2', '3', '4', '5', '6', '7', '8', '9',
/* 20-63 */	'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
		'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
		'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
		'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
	  };
		if ((n = atoi(from + 1)) > 63) {
			p = msg_print(sp, from, &nf);
			msgq(sp, M_ERR,
		     "228|Termcap has no code for the %s function key", p);
			if (nf)
				FREE_SPACE(sp, p, 0);
			return (1);
		}
		(void)snprintf(keyname, sizeof(keyname),
		    "%c%c", n <= 10 ? 'k' : 'F', codes[n]);
		sbp = sbuf;
		t = tgetstr(keyname, &sbp);
	}
#endif
	if (t == NULL) {
		p = msg_print(sp, from, &nf);
		msgq(sp, M_ERR, "229|This terminal has no %s key", p);
		if (nf)
			FREE_SPACE(sp, p, 0);
		return (1);
	}

	nlen = snprintf(keyname,
	    sizeof(keyname), "function key %d", atoi(from + 1));
	return (seq_set(sp, keyname, nlen,
	    t, strlen(t), to, tlen, stype, SEQ_NOOVERWRITE | SEQ_SCREEN));
}

/*
 * cl_ssize --
 *	Set the terminal size.
 */
int
cl_ssize(sp, sigwinch)
	SCR *sp;
	int sigwinch;
{
#ifdef TIOCGWINSZ
	struct winsize win;
#endif
	size_t col, row;
	int nf, rval;
	ARGS *argv[2], a, b;
	char *p, *s, buf[2048];

	/*
	 * Get the screen rows and columns.  If the values are wrong, it's
	 * not a big deal -- as soon as the user sets them explicitly the
	 * environment will be set and the screen package will use the new
	 * values.
	 *
	 * Try TIOCGWINSZ.
	 */
	row = col = 0;
#ifdef TIOCGWINSZ
	if (ioctl(STDERR_FILENO, TIOCGWINSZ, &win) != -1) {
		row = win.ws_row;
		col = win.ws_col;
	}
#endif
	/* If here because of suspend or a signal, only trust TIOCGWINSZ. */
	if (sigwinch) {
		/*
		 * Somebody didn't get TIOCGWINSZ right, or has suspend
		 * without window resizing support.  The user just lost,
		 * but there's nothing we can do.
		 */
		if (row == 0 || col == 0)
			return (1);

		/*
		 * SunOS systems deliver SIGWINCH when windows are uncovered
		 * as well as when they change size.  In addition, we call
		 * here when continuing after being suspended since the window
		 * may have changed size.  Since we don't want to background
		 * all of the screens just because the window was uncovered,
		 * ignore the signal if there's no change.
		 */
		if (row == O_VAL(sp, O_LINES) && col == O_VAL(sp, O_COLUMNS))
			return (1);

		goto sigw;
	}

	/*
	 * !!!
	 * If TIOCGWINSZ failed, or had entries of 0, try termcap.  This
	 * routine is called before any termcap or terminal information
	 * has been set up.  If there's no TERM environmental variable set,
	 * let it go, at least ex can run.
	 */
	if (row == 0 || col == 0) {
		if ((s = getenv("TERM")) == NULL)
			goto noterm;
#ifdef SYSV_CURSES
		if (row == 0)
			if ((rval = tigetnum("lines")) < 0)
				msgq(sp, M_SYSERR, "tigetnum: lines");
			else
				row = rval;
		if (col == 0)
			if ((rval = tigetnum("cols")) < 0)
				msgq(sp, M_SYSERR, "tigetnum: cols");
			else
				col = rval;
#else
		switch (tgetent(buf, s)) {
		case -1:
			msgq(sp, M_SYSERR, "tgetent: %s", s);
			return (1);
		case 0:
			p = msg_print(sp, s, &nf);
			msgq(sp, M_ERR, "096|%s: unknown terminal type", p);
			if (nf)
				FREE_SPACE(sp, p, 0);
			return (1);
		}
		if (row == 0)
			if ((rval = tgetnum("li")) < 0) {
				p = msg_print(sp, s, &nf);
				msgq(sp, M_ERR,
			    "097|no \"li\" terminal capability for %s", p);
				if (nf)
					FREE_SPACE(sp, p, 0);
			} else
				row = rval;
		if (col == 0)
			if ((rval = tgetnum("co")) < 0) {
				p = msg_print(sp, s, &nf);
				msgq(sp, M_ERR,
			    "098|no \"co\" terminal capability for %s", p);
				if (nf)
					FREE_SPACE(sp, p, 0);
			} else
				col = rval;
#endif
	}

	/* If nothing else, well, it's probably a VT100. */
noterm:	if (row == 0)
		row = 24;
	if (col == 0)
		col = 80;

	/* POSIX 1003.2 requires the environment to override. */
	if ((s = getenv("LINES")) != NULL)
		row = strtol(s, NULL, 10);
	if ((s = getenv("COLUMNS")) != NULL)
		col = strtol(s, NULL, 10);

sigw:	a.bp = buf;
	b.bp = NULL;
	b.len = 0;
	argv[0] = &a;
	argv[1] = &b;;

	/* Use the options code to accomplish the change. */
	a.len = snprintf(buf, sizeof(buf), "lines=%u", row);
	if (opts_set(sp, argv, 1, NULL))
		return (1);
	a.len = snprintf(buf, sizeof(buf), "columns=%u", col);
	if (opts_set(sp, argv, 1, NULL))
		return (1);
	return (0);
}
