/*
 * STEVIE - Simply Try this Editor for VI Enthusiasts
 *
 * Code Contributions By : Tim Thompson           twitch!tjt
 *                         Tony Andrews           onecom!wldrdg!tony 
 *                         G. R. (Fred) Walter    watmath!watcgl!grwalter 
 */

#include "stevie.h"

/*
 * The following variable is set (in cursupdate) to the number of physical
 * lines taken by the line the cursor is on. We use this to avoid extra calls
 * to plines(). The optimized routines updateline() and redrawline()
 * make sure that the size of the cursor line hasn't changed. If so, lines below
 * the cursor will move up or down and we need to call the routines
 * updateNextscreen() and updateRealscreen() to examine the entire screen. 
 */
static int      Cline_size;	/* size (in rows) of the cursor line */
static int      Cline_row;	/* starting row of the cursor line */

/*
 * updateline() - like updateNextscreen() but only for cursor line 
 *
 * This determines whether or not we need to call updateNextscreen() to examine the
 * entire screen for changes. This occurs if the size of the cursor line
 * (in rows) hasn't changed.
 */
void
updateline()
{
    int             row, col;
    register char  *screenp;
    LPTR            memp;
    register char  *nextrow;
    char            extra[16];
    int             nextra = 0;
    char            c;
    int             n;
    bool_t          eof;

    MustRedrawLine = TRUE;

#ifndef AMIGA
    if (MustRedrawScreen) {
	msg("STEVIE internal error: updateline called");
	sleep(5);
    }
#endif

    screenp = Nextscreen + (Cline_row * Columns);

    memp = *Curschar;
    memp.index = 0;

    eof = FALSE;
    col = 0;
    row = Cline_row;

    while (!eof) {

	/* Get the next character to put on the screen. */

	/*
	 * The 'extra' array contains the extra stuff that is inserted to
	 * represent special characters (tabs, and other non-printable stuff.
	 * The order in the 'extra' array is reversed. 
	 */

	if (nextra > 0)
	    c = extra[--nextra];
	else {
	    c = gchar(&memp);
	    if (inc(&memp) == -1)
		eof = TRUE;
	    /*
	     * when getting a character from the file, we may have to turn it
	     * into something else on the way to putting it into
	     * 'Nextscreen'. 
	     */
	    if (c == TAB && !P(P_LS)) {
		strcpy(extra, "        ");
		/* tab amount depends on current column */
		nextra = ((P(P_TS) - 1) - col % P(P_TS));
		c = ' ';
	    } else if (c == NUL && P(P_LS)) {
		extra[0] = NUL;
		nextra = 1;
		c = '$';
	    } else if (c != NUL && (n = chars[c].ch_size) > 1) {
		char           *p;
		nextra = 0;
		p = chars[c].ch_str;
		/* copy 'ch-str'ing into 'extra' in reverse */
		while (n > 1)
		    extra[nextra++] = p[--n];
		c = p[0];
	    }
	}

	if (c == NUL) {
	    row++;
	    /* get pointer to start of next row */
	    nextrow = &Nextscreen[row * Columns];
	    /* blank out the rest of this row */
	    while (screenp != nextrow)
		*screenp++ = ' ';
	    col = 0;
	    break;
	}
	if (col >= Columns) {
	    row++;
	    col = 0;
	}
	/* store the character in Nextscreen */
	*screenp++ = c;
	col++;
    }
    if ((row - Cline_row) == Cline_size)
	updateNextscreen();
}

/*
 * redrawline 
 *
 * Like updateRealscreen() but only for the cursor line. 
 */
void
redrawline()
{
    register char  *np = Nextscreen + (Cline_row * Columns);
    register char  *rp = Realscreen + (Cline_row * Columns);
    register char  *endline;
    int             row, col;
    int             gorow = -1, gocol = -1;

    if (RedrawingDisabled)
	return;

    if (!MustRedrawLine && !MustRedrawScreen)
	return;

    if (MustRedrawScreen) {
	msg("STEVIE internal error: redrawline called");
	sleep(5);
    }
    endline = np + (Cline_size * Columns);

    row = Cline_row;
    col = 0;

    outstr(T_CI);		/* disable cursor */

    for (; np < endline; np++, rp++) {
	/* If desired screen (contents of Nextscreen) does not */
	/* match what's really there, put it there. */
	if (*np != *rp) {
	    /* if we are positioned at the right place, */
	    /* we don't have to use windgoto(). */
	    if (gocol != col || gorow != row) {
		/*
		 * If we're just off by one, don't send an entire esc. seq.
		 * (this happens a lot!) 
		 */
		if (gorow == row && gocol + 1 == col) {
		    outchar(*(np - 1));
		    gocol++;
		} else
		    windgoto(gorow = row, gocol = col);
	    }
	    outchar(*rp = *np);
	    gocol++;
	}
	if (++col >= Columns) {
	    col = 0;
	    row++;
	}
    }
    outstr(T_CV);		/* enable cursor again */

    MustRedrawScreen = FALSE;
}

void
screenclear()
{
    char           *rp, *np;
    char           *end;

    outstr(T_ED);		/* clear the display */

    rp = Realscreen;
    end = Realscreen + Rows * Columns;
    np = Nextscreen;

    /* blank out the stored screens */
    while (rp != end)
	*rp++ = *np++ = ' ';
}

void
cursupdate()
{
    LPTR           *p;
    char            c;
    int             incr, nlines;
    int             i;
    int             didincr;

    if (bufempty()) {		/* special case - file is empty */
	*Topchar = *Filemem;
	*Curschar = *Filemem;
    } else if (LINEOF(Curschar) < LINEOF(Topchar)) {
	nlines = cntllines(Curschar, Topchar);
	/*
	 * if the cursor is above the top of the screen, put it at the top of
	 * the screen.. 
	 */
	*Topchar = *Curschar;
	Topchar->index = 0;
	/*
	 * ... and, if we weren't very close to begin with, we scroll so that
	 * the line is close to the middle. 
	 */
	if (nlines > Rows / 3) {
	    for (i = 0, p = Topchar; i < Rows / 3; i++, *Topchar = *p)
		if ((p = prevline(p)) == NULL)
		    break;
	} else
	    s_ins(0, nlines - 1);
	updateNextscreen();
    } else if (LINEOF(Curschar) >= LINEOF(Botchar)) {
	nlines = cntllines(Botchar, Curschar);
	/*
	 * If the cursor is off the bottom of the screen, put it at the top
	 * of the screen.. ... and back up 
	 */
	if (nlines > Rows / 3) {
	    p = Curschar;
	    for (i = 0; i < (2 * Rows) / 3; i++)
		if ((p = prevline(p)) == NULL)
		    break;
	    *Topchar = *p;
	} else {
	    scrollup(nlines);
	}
	updateNextscreen();
    }
    Cursrow = Curscol = Cursvcol = 0;
    for (p = Topchar; p->linep != Curschar->linep; p = nextline(p))
	Cursrow += plines(p);

    Cline_row = Cursrow;
    Cline_size = plines(p);

    for (i = 0; i <= Curschar->index; i++) {
	c = Curschar->linep->s[i];
	/* A tab gets expanded, depending on the current column */
	if (c == TAB && !P(P_LS))
	    incr = P(P_TS) - (Curscol % P(P_TS));
	else
	    incr = chars[c].ch_size;
	Curscol += incr;
	Cursvcol += incr;
	if (Curscol >= Columns) {
	    Curscol -= Columns;
	    Cursrow++;
	    didincr = TRUE;
	} else
	    didincr = FALSE;
    }
    if (didincr)
	Cursrow--;

    if (c == TAB && State == NORMAL && !P(P_LS)) {
	Curscol--;
	Cursvcol--;
    } else {
	Curscol -= incr;
	Cursvcol -= incr;
    }
    if (Curscol < 0)
	Curscol += Columns;

    if (set_want_col) {
	Curswant = Cursvcol;
	set_want_col = FALSE;
    }
}

/*
 * The rest of the routines in this file perform screen manipulations. The
 * given operation is performed physically on the screen. The corresponding
 * change is also made to the internal screen image. In this way, the editor
 * anticipates the effect of editing changes on the appearance of the screen.
 * That way, when we call screenupdate a complete redraw isn't usually
 * necessary. Another advantage is that we can keep adding code to anticipate
 * screen changes, and in the meantime, everything still works. 
 */

/*
 * s_ins(row, nlines) - insert 'nlines' lines at 'row' 
 */
void
s_ins(row, nlines)
    int             row;
    int             nlines;
{
    char           *s, *d;	/* src & dest for block copy */
    char           *e;		/* end point for copy */
    int             i;

    if ((T_IL[0] == NUL) || RedrawingDisabled || nlines <= 0)
	return;

    /*
     * It "looks" better if we do all the inserts at once 
     */
    outstr(T_SC);		/* save position */

    if (T_IL_B[0] == NUL) {
	for (i = 0; i < nlines; i++) {
	    windgoto(row, 0);
	    outstr(T_IL);
	}
    } else {
	windgoto(row, 0);
	outstr(T_IL);
	if (nlines >= 10)
	    outchar((char) (nlines / 10 + '0'));
	outchar((char) (nlines % 10 + '0'));
	outstr(T_IL_B);
    }

    windgoto(Rows - 1, 0);	/* delete any garbage that may have */
    outstr(T_EL);		/* been shifted to the bottom line */

    outstr(T_RC);		/* restore the cursor position */

    /*
     * Now do a block move to update the internal screen image 
     */
    d = Realscreen + (Columns * (Rows - 1)) - 1;
    s = d - (Columns * nlines);
    e = Realscreen + (Columns * row);

    while (s >= e)
	*d-- = *s--;

    /*
     * Clear the inserted lines 
     */
    s = Realscreen + (row * Columns);
    e = s + (nlines * Columns);
    while (s < e)
	*s++ = ' ';
}

/*
 * s_del(row, nlines) - delete 'nlines' lines at 'row' 
 */
void
s_del(row, nlines)
    int             row;
    int             nlines;
{
    char           *s, *d, *e;
    int             i;

    if ((T_DL[0] == NUL) || RedrawingDisabled || nlines <= 0)
	return;

    outstr(T_SC);		/* save position */

    windgoto(Rows - 1, 0);	/* delete any garbage that */
    outstr(T_EL);		/* was on the status line */

    /* delete the lines */
    if (T_DL_B[0] == NUL) {
	for (i = 0; i < nlines; i++) {
	    windgoto(row, 0);
	    outstr(T_DL);	/* delete a line */
	}
    } else {
	windgoto(row, 0);
	outstr(T_DL);
	if (nlines >= 10)
	    outchar((char) (nlines / 10 + '0'));
	outchar((char) (nlines % 10 + '0'));
	outstr(T_DL_B);
    }

    outstr(T_RC);		/* restore position */

    /*
     * do a block move to update the internal image 
     */
    d = Realscreen + (row * Columns);
    s = d + (nlines * Columns);
    e = Realscreen + ((Rows - 1) * Columns);

    while (s < e)
	*d++ = *s++;

    while (d < e)		/* clear the lines at the bottom */
	*d++ = ' ';
}
