/*-
 * 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[] = "@(#)svi_screen.c	9.13 (Berkeley) 2/16/95";
#endif /* not lint */

#include <sys/types.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 <db.h>
#include <regex.h>

#include "vi.h"
#include "svi_screen.h"
#include "../cl/cl.h"
#include "../sex/sex_screen.h"
#include "../vi/vcmd.h"
#include "../xaw/xaw.h"

static void svi_dtoh __P((SCR *));

/*
 * svi_screen_copy --
 *	Copy to a new screen.
 */
int
svi_screen_copy(orig, sp)
	SCR *orig, *sp;
{
	SVI_PRIVATE *osvi, *nsvi;

	/* Create the private screen structure. */
	CALLOC_RET(orig, nsvi, SVI_PRIVATE *, 1, sizeof(SVI_PRIVATE));
	sp->svi_private = nsvi;

/* INITIALIZED AT SCREEN CREATE. */
	/* Invalidate the line size cache. */
	SVI_SCR_CFLUSH(nsvi);

/* PARTIALLY OR COMPLETELY COPIED FROM PREVIOUS SCREEN. */
	if (orig == NULL) {
	} else {
		osvi = SVP(orig);
		nsvi->srows = osvi->srows;

		nsvi->scr_addnstr = osvi->scr_addnstr;
		nsvi->scr_addstr = osvi->scr_addstr;
		nsvi->scr_clear = osvi->scr_clear;
		nsvi->scr_clrtoeol = osvi->scr_clrtoeol;
		nsvi->scr_cursor = osvi->scr_cursor;
		nsvi->scr_deleteln = osvi->scr_deleteln;
		nsvi->scr_end = osvi->scr_end;
		nsvi->scr_insertln = osvi->scr_insertln;
		nsvi->scr_inverse = osvi->scr_inverse;
		nsvi->scr_linverse = osvi->scr_linverse;
		nsvi->scr_move = osvi->scr_move;
		nsvi->scr_refresh = osvi->scr_refresh;
		nsvi->scr_restore = osvi->scr_restore;
		nsvi->scr_size = osvi->scr_size;
	}
	return (0);
}

/*
 * svi_screen_end --
 *	End a screen.
 */
int
svi_screen_end(sp)
	SCR *sp;
{
	if (HMAP != NULL)		/* Free the screen map. */
		FREE(HMAP, SIZE_HMAP(sp) * sizeof(SMAP));

					/* Free private memory. */
	FREE(SVP(sp), sizeof(SVI_PRIVATE));
	sp->svi_private = NULL;

	return (0);
}

/*
 * We use a single curses "window" for each vi screen.  The model would be
 * simpler with two windows (one for the text, and one for the modeline)
 * because scrolling the text window down would work correctly then, not
 * affecting the mode line.  As it is we have to play games to make it look
 * right.  The reason for this choice is that it would be difficult for
 * curses to optimize the movement, i.e. detect that the downward scroll
 * isn't going to change the modeline, set the scrolling region on the
 * terminal and only scroll the first part of the text window.
 *
 * svi_screen_edit --
 *	Main vi curses screen loop.
 */
int
svi_screen_edit(sp)
	SCR *sp;
{
	FILE *sv_stdfp;
	GS *gp;
	MARK rp;
	SCR *tsp;
	SVI_PRIVATE *svp;
	int ecurses, escreen, force, rval;
	char *p;

	svp = SVP(sp);
	escreen = ecurses = rval = 0;

	/* Save current function set, output pointer. */
	screen_fcopy(sp, 1);

	/* Initialize the physical screen. */
	switch (F_ISSET(sp, S_VI)) {
	case S_VI_CURSES:
		if (cl_init(sp)) {
			escreen = 1;
			goto err;
		}
		break;
	case S_VI_XAW:
		if (xaw_init(sp)) {
			escreen = 1;
			goto err;
		}
		break;
	default:
		abort();
	}
	ecurses = 1;

	/* Initialize the function set. */
					/* e_bell set by screen init. */
	sp->e_busy	= svi_busy;
	sp->e_change	= svi_change;
	sp->e_clrtoeos	= svi_clrtoeos;
	sp->e_confirm	= svi_confirm;
					/* e_fmap set by screen init. */
	sp->e_refresh	= svi_refresh;
					/* e_ssize set by main init. */
					/* e_suspend set by screen init. */

	/* Local screen initialization. */
	if (svi_screen_init(sp))
		goto err;

	/*
	 * The resize bit is probably set, as a result of the terminal being
	 * set.  We clear it as we just finished initializing the screen.
	 * However, we will want to fill in the map from scratch, so provide
	 * a line number (just in case) and set the reformat flag.
	 */
	HMAP->lno = 1;
	F_CLR(sp, S_SCR_RESIZE);
	F_SET(sp, S_SCR_REFORMAT);

	for (gp = sp->gp;;) {
		/*
		 * If there was a posted command, execute it now, otherwise,
		 * reset the cursor and run vi.  If vi fails, data structures
		 * may be corrupted, be extremely careful what you free up.
		 */
		if (gp->postcmd == NULL) {
			F_SET(SVP(sp), SVI_CUR_INVALID);
			/*
			 * XXX
			 * We may have put up various one-line messages while
			 * in other screens -- clear the wait-for-key flag.
			 * This belongs somewhere else...
			 */
			F_CLR(sp, S_SCR_UMODE);

			/* Make ex display to a screen scrolling function. */
			sv_stdfp = sp->stdfp;
			if ((sp->stdfp = fwopen(sp, svi_ex_write)) == NULL) {
				msgq(sp, M_SYSERR, "fwopen");
				goto err;
			}

			if (vi(sp)) {
				(void)rcv_sync(sp,
				    RCV_EMAIL | RCV_ENDSESSION | RCV_PRESERVE);
				escreen = 1;
				goto err;
			}

			(void)fclose(sp->stdfp);
			sp->stdfp = sv_stdfp;
		} else {
			if (svi_refresh(sp))
				goto err;

			MALLOC(sp, p, char *, gp->postcmd_len);
			if (p != NULL)
				memmove(p, gp->postcmd, gp->postcmd_len);
			free(gp->postcmd);
			gp->postcmd = NULL;
			if (p != NULL) {
				(void)svi_ex_run(sp, &rp, p, gp->postcmd_len);
				free(p);
			}
			if (!F_ISSET(sp, S_MAJOR_CHANGE)) {
				sp->lno = rp.lno;
				sp->cno = rp.cno;
				continue;
			}
		}

		force = 0;
		switch (F_ISSET(sp, S_MAJOR_CHANGE)) {
		case S_EXIT_FORCE:
			force = 1;
			/* FALLTHROUGH */
		case S_EXIT:
			F_CLR(sp, S_EXIT_FORCE | S_EXIT);
			if (file_end(sp, NULL, force))
				break;
			/*
			 * !!!
			 * NB: sp->frp may now be NULL, if it was a tmp file.
			 *
			 * Find a new screen.
			 */
			(void)svi_join(sp, NULL, NULL, &tsp);
			if (tsp == NULL)
				(void)svi_swap(sp, &tsp, NULL);
			if (tsp == NULL) {
				escreen = 1;
				goto ret;
			}
			(void)screen_end(sp);
			sp = tsp;
			break;
		case S_SCR_RESIZE:
		case 0:					/* Exit vi mode. */
			svi_dtoh(sp);
			goto ret;
		case S_SSWITCH:
			/* Paint the old screen's status line. */
			(void)msg_status(sp, sp->lno, 0);

			/* Save the old screen's cursor information. */
			sp->frp->lno = sp->lno;
			sp->frp->cno = sp->cno;
			F_SET(sp->frp, FR_CURSORSET);

			/* Clear the flag and switch. */
			F_CLR(sp, S_SSWITCH);
			sp = sp->nextdisp;
			break;
		default:
			abort();
		}
	}

	if (0) {
err:		rval = 1;
	}

ret:	screen_fcopy(sp, 0);			/* Restore the function set. */
	if (ecurses && SVP(sp)->scr_end(sp))	/* Screen end (uses sp). */
		rval = 1;
	if (escreen && screen_end(sp))		/* Local screen end. */
		rval = 1;
	return (rval);
}

/*
 * svi_screen_init --
 *	Initialize the ex screen functions.
 */
int
svi_screen_init(sp)
	SCR *sp;
{
	SVI_PRIVATE *svp;

	svp = SVP(sp);
	/*
	 * The first screen in the list gets it all.  All other screens
	 * are hidden and lose their maps.
	 */
	svi_dtoh(sp);

	/* Initialize terminal values. */
	svp->srows = O_VAL(sp, O_LINES);

	/*
	 * Initialize screen values.
	 *
	 * Small windows: see svi/svi_refresh.c:svi_refresh, section 3b.
	 *
	 * Setup:
	 *	t_minrows is the minimum rows to display
	 *	t_maxrows is the maximum rows to display (rows - 1)
	 *	t_rows is the rows currently being displayed
	 */
	sp->rows = svp->srows;
	sp->cols = O_VAL(sp, O_COLUMNS);
	sp->woff = 0;
	sp->t_rows = sp->t_minrows = O_VAL(sp, O_WINDOW);
	if (sp->rows != 1) {
		if (sp->t_rows > sp->rows - 1) {
			sp->t_minrows = sp->t_rows = sp->rows - 1;
			msgq(sp, M_INFO,
			    "218|Windows option value is too large, max is %u",
			    sp->t_rows);
		}
		sp->t_maxrows = sp->rows - 1;
	} else
		sp->t_maxrows = 1;

	/* Create the screen map. */
	CALLOC(sp, HMAP, SMAP *, SIZE_HMAP(sp), sizeof(SMAP));
	if (HMAP == NULL) {
		svp->scr_end(sp);
		return (1);
	}
	TMAP = HMAP + (sp->t_rows - 1);
	return (0);
}

/*
 * svi_dtoh --
 *	Move all but the current screen to the hidden queue.
 */
static void
svi_dtoh(sp)
	SCR *sp;
{
	SCR *tsp;
	int hidden;

	/* Move all screens to the hidden queue, tossing screen maps. */
	for (hidden = 0;
	    (tsp = sp->gp->dq.cqh_first) != (void *)&sp->gp->dq; ++hidden) {
		if (_HMAP(tsp) != NULL) {
			FREE(_HMAP(tsp), SIZE_HMAP(tsp) * sizeof(SMAP));
			_HMAP(tsp) = NULL;
		}
		SIGBLOCK(sp->gp);
		CIRCLEQ_REMOVE(&sp->gp->dq, tsp, q);
		CIRCLEQ_INSERT_TAIL(&sp->gp->hq, tsp, q);
		SIGUNBLOCK(sp->gp);
	}

	/* Move current screen back to the display queue. */
	SIGBLOCK(sp->gp);
	CIRCLEQ_REMOVE(&sp->gp->hq, sp, q);
	CIRCLEQ_INSERT_TAIL(&sp->gp->dq, sp, q);
	SIGUNBLOCK(sp->gp);

	/*
	 * XXX
	 * Don't bother internationalizing this message, it's going to
	 * go away as soon as we have one-line screens.  --TK
	 */
	if (hidden > 1)
		msgq(sp, M_INFO,
		    "%d screens backgrounded; use :display to list them",
		    hidden - 1);
}
