/*
 * 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()
{
    register int    row;
    register int    col;
    register char  *screenp;
    register char   c;
    LPtr            memp;
    register char  *nextrow;
    char            extra[16];
    char           *p_extra;
    int             n_extra;
    int             n;
    bool_t          eof;
    int             lno;	/* number of the line we're doing */
    int             coff;	/* column offset */

    MustRedrawLine = TRUE;

    coff = P(P_NU) ? 8 : 0;

    /*
     * This should be done more efficiently. 
     */
    if (P(P_NU))
	lno = cntllines(Filemem, Curschar);

    screenp = Nextscreen + (Cline_row * Columns);

    memp = *Curschar;
    memp.index = 0;

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

    p_extra = NULL;
    n_extra = 0;
    if (P(P_NU)) {
	strcpy(extra, mkline(lno));
	p_extra = extra;
	n_extra = 8;
    }
    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 (n_extra > 0) {
	    c = *p_extra++;
	    n_extra--;
	} 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, "               ");
		p_extra = extra;
		/* tab amount depends on current column */
		n_extra = ((P(P_TS) - 1) - (col - coff) % P(P_TS));
		c = ' ';
	    } else if (c == NUL && P(P_LS)) {
		extra[0] = NUL;
		p_extra = extra;
		n_extra = 1;
		c = '$';
	    } else if (c != NUL && (n = chars[c].ch_size) > 1) {
		p_extra = chars[c].ch_str;
		c = *p_extra++;
		n_extra = n - 1;
	    }
	}

	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++ = ' ';
	    break;
	}
	if (col >= Columns) {
	    row++;
	    col = 0;
	}
	/* store the character in Nextscreen */
	*screenp++ = c;
	col++;
    }
    if ((row - Cline_row) != Cline_size) {
	updateNextscreen(VALID_TO_CURSCHAR);
    }
}

/*
 * 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;
    register 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;
}

/*
 * prt_line() - print the given line
 */
void
prt_line(s)
    char           *s;
{
    register int    si = 0;
    register int    c;
    register int    col = 0;

    char            extra[16];
    int             n_extra = 0;
    int             n;

    for (;;) {

	if (n_extra > 0)
	    c = extra[--n_extra];
	else {
	    c = s[si++];
	    if (c == TAB && !P(P_LS)) {
		strcpy(extra, "                ");
		/* tab amount depends on current column */
		n_extra = (P(P_TS) - 1) - col % P(P_TS);
		c = ' ';
	    } else if (c == NUL && P(P_LS)) {
		extra[0] = NUL;
		n_extra = 1;
		c = '$';
	    } else if (c != NUL && (n = chars[c].ch_size) > 1) {
		char           *p;

		n_extra = 0;
		p = chars[c].ch_str;
		/* copy 'ch-str'ing into 'extra' in reverse */
		while (n > 1)
		    extra[n_extra++] = p[--n];
		c = p[0];
	    }
	}

	if (c == NUL)
	    break;

	outchar(c);
	col++;
    }
}

void
screenclear()
{
    register char  *rp;
    register char  *np;
    register char  *end;
    register int    i;

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

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

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

    /* clear screen info */
    for (i = 0; i < Rows; i++) {
	LinePointers[i] = NULL;
	LineSizes[i] = '\0';
    }
    NumLineSizes = 0;
}

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

    if (bufempty()) {		/* special case - file is empty */
	*Topchar = *Filemem;
	*Curschar = *Filemem;
	for (i = 0; i < Rows; i++)
	    LineSizes[i] = 0;
    } 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) {
	    p = Topchar;
	    for (i = 0; i < Rows / 3; i += plines(p)) {
		pp = prevline(p);
		if (pp == NULL)
		    break;
		p = pp;
	    }
	    *Topchar = *p;
	} else
	    s_ins(0, nlines - 1, Rows, Columns);
	updateNextscreen(VALID);
    } 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 += plines(p)) {
		pp = prevline(p);
		if (pp == NULL)
		    break;
		p = pp;
	    }
	    *Topchar = *p;
	} else {
	    scrollup(nlines);
	}
	updateNextscreen(VALID);
    }
    Cursrow = Curscol = Cursvcol = i = 0;
    for (p = Topchar; p->linep != Curschar->linep; p = nextline(p))
	Cursrow += LineSizes[i++];

    if (P(P_NU))
	Curscol = 8;

    Cline_row = Cursrow;
    if (i >= NumLineSizes) {	/* Should only happen with a line that is too
				 * long to fit on the last screen line. */
	Cline_size = 0;
    } else {
	Cline_size = LineSizes[i];

	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) - (Cursvcol % 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, total_rows, columns) - insert 'nlines' lines at 'row' 
 */
void
s_ins(row, nlines, total_rows, columns)
    int             row;
    int             nlines;
    int		    total_rows;
    int		    columns;
{
    register char  *s;	/* src for block copy */
    register char  *d;	/* dest for block copy */
    register char  *e;	/* end point for copy */
    int             i;

    if (nlines > (total_rows - 1 - row))
	nlines = total_rows - 1 - row;

    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(total_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 * (total_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, total_rows, columns) - delete 'nlines' lines at 'row' 
 */
void
s_del(row, nlines, total_rows, columns)
    int             row;
    int             nlines;
    int		    total_rows;
    int		    columns;
{
    register char  *s;
    register char  *d;
    register char  *e;
    int             i;

    if (nlines > (total_rows - 1 - row))
	nlines = total_rows - 1 - row;

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

    outstr(T_SC);		/* save position */

    windgoto(total_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 + ((total_rows - 1) * columns);

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

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