/*
*       Text line handling.
* The functions in this file
* are a general set of line management
* utilities. They are the only routines that
* touch the text. They also touch the buffer
* and window structures, to make sure that the
* necessary updating gets done. There are routines
* in this file that handle the kill buffer too.
* It isn't here for any good reason.
*
* Note that this code only updates the dot and
* mark values in the window list. Since all the code
* acts on the current window, the buffer that we
* are editing must be being displayed, which means
* that "b_nwnd" is non zero, which means that the
* dot and mark values in the buffer headers are
* nonsense.
*/

#include    "def.h"

void l_fix_up ();

extern    char    MSG_cnt_alloc[];
#if RUNCHK
extern    char    ERR_no_alloc[];
extern    char    ERR_db_dalloc[];
extern    char    ERR_lock[];
extern    char    ERR_lock_del[];
#endif

extern  LINE    *cur_pat;
extern  LINE    *cur_mask;
extern  bool    read_pat_mode;
extern  BUFFER  sav_buf;

/*
* This routine allocates a block
* of memory large enough to hold a LINE
* containing "size" characters. Return a pointer
* to the new block, or NULL if there isn't
* any memory left. Print a message in the
* message line if no space.
*/
LINE * lalloc (size)
register int    size;
{
	register    LINE * lp;
	char    buf[NCOL], buf1[NCOL];
#if RUNCHK
	if (read_pat_mode)
		printf (ERR_no_alloc);
#endif

	if ((lp = (LINE *) malloc (sizeof (LINE) + size)) == NULL)
	{
		sprintf (buf1, MSG_cnt_alloc, R_POS_FMT(curwp));
		sprintf (buf, buf1, (A32)size);
		err_echo (buf);
		curbp -> b_flag |= BFBAD;/* may be trashed */
		curwp -> w_flag |= WFMODE;
		update ();
		return (NULL);
	}
	lp -> l_size = size;
	lp -> l_used = 0;
	lp -> l_file_offset = 0;    /* set resonable initial value */
	return (lp);
}


/*
* Delete line "lp". Fix all of the
* links that might point at it (they are
* moved to offset 0 of the next line.
* Unlink the line from whatever buffer it
* might be in. Release the memory. The
* buffers are updated too; the magic conditions
* described in the above comments don't hold
* here.
*/

void lfree (lp)
register    LINE * lp;
{
	register    BUFFER * bp;
	register    WINDOW * wp;

#if RUNCHK
	if (read_pat_mode)
		printf (ERR_db_dalloc);
#endif

	wp = wheadp;
	while (wp != NULL)
	{
		if (wp -> w_linep == lp)
		{
			wp -> w_linep = lp -> l_fp;
			wp -> w_loff = 0;
		}

		if (wp -> w_dotp == lp)
		{
			wp -> w_dotp = lp -> l_fp;
			wp -> w_doto = 0;
		}

		if (wp -> w_markp == lp)
		{
			wp -> w_markp = lp -> l_fp;
			wp -> w_marko = 0;
		}

		wp = wp -> w_wndp;
	}

	bp = bheadp;
	while (bp != NULL)
	{
		if (bp -> b_nwnd == 0)
		{
			if (bp -> b_dotp == lp)
			{
				bp -> b_dotp = lp -> l_fp;
				bp -> b_doto = 0;
			}

			if (bp -> b_markp == lp)
			{
				bp -> b_markp = lp -> l_fp;
				bp -> b_marko = 0;
			}
		}
		bp = bp -> b_bufp;
	}

	lp -> l_bp -> l_fp = lp -> l_fp;
	lp -> l_fp -> l_bp = lp -> l_bp;
	free ((char *) lp);
}


/*
* This routine gets called when
* a character is changed in place in the
* current buffer. It updates all of the required
* flags in the buffer and window system. The flag
* used is passed as an argument; if the buffer is being
* displayed in more than 1 window we change EDIT to
* HARD. Set MODE if the mode line needs to be
* updated (the "*" has to be set).
*/
void lchange (flag)
register int    flag;
{
	register    WINDOW * wp;

	if (curbp -> b_nwnd != 1)   /* Ensure hard.     */
		flag = WFHARD;
	if ((curbp -> b_flag & BFCHG) == 0)
	{
		/* First change, so     */
		flag |= WFMODE;         /* update mode lines.   */
		curbp -> b_flag |= BFCHG;
	}

	wp = wheadp;
	while (wp != NULL)
	{
		if (wp -> w_bufp == curbp)
			wp -> w_flag |= flag;
		wp = wp -> w_wndp;
	}
}


/*
 *  Break the line "dotp" in two at the position "doto."
 */

LINE *l_break_in_two (lp, lo, extra)
register LINE  *lp;
register LPOS  lo, extra;
{
	register LINE  *new_lp;
	register D8    *cp1;
	register D8    *cp2;
	LPOS	cnt, i;

	i = 0;
	cnt = lp -> l_used - lo;
	if ((new_lp = lalloc (cnt + extra)) == NULL)
		return (NULL);

	cp1 = &lp -> l_text[lo];  /* starting location, source */
	cp2 = &new_lp -> l_text[0];  /* starting location, destination */

	/* kill bytes in the current line */
	while (i++ < cnt)
	{
		*cp2++ = *cp1++;
	}
	lp -> l_used -= cnt;
	new_lp -> l_used = cnt;
	new_lp -> l_file_offset = new_lp -> l_file_offset + lo;

	/* insert into chain */
	new_lp -> l_fp = lp -> l_fp;
	lp -> l_fp = new_lp;
	new_lp -> l_bp = lp;
	new_lp -> l_fp -> l_bp = new_lp;
	return (new_lp);
}

/*
* Insert "n" copies of the character "c"
* at the current location of dot. In the easy case
* all that happens is the text is stored in the line.
* Always allocate some extra space in line so that edit 
* will be faster next time but will save space in the general case.
* In the hard case, the line has to be reallocated.
* When the window list is updated, take special
* care; I screwed it up once. You always update dot
* in the current window. You update mark, and a
* dot in another window, if it is greater than
* the place where you did the insert. Return TRUE
* if all is well, and FALSE on errors.
*/
bool linsert (n, c)
uchar   c;
{
	register D8    *cp1;
	register D8    *cp2;
	register    LINE * lp1;
	register    LINE * lp2;
	register short  doto;
	register int    i;
	register    WINDOW * wp;

#if RUNCHK
	/* check that buffer size can be changed */
	if (curbp -> b_flag & BFSLOCK)
	{
		writ_echo (ERR_lock);
		return (FALSE);
	}
#endif

	lchange (WFMOVE);
	lp1 = curwp -> w_dotp;      /* Current line     */
	if (lp1 == curbp -> b_linep)
	{
		/* At the end: special  */
		/* break the current line at the end */
		if ((lp2 = l_break_in_two (lp1, lp1 -> l_used, (LPOS)n + NBLOCK)) == NULL)
			return (FALSE);
		for (i = 0; i < n; ++i)     /* Add the characters   */
			lp2 -> l_text[i] = c;
		lp2 -> l_used = n;
		curwp -> w_dotp = lp2;
		curwp -> w_doto = n;
		return (TRUE);
	}

	doto = curwp -> w_doto;     /* Save for later.  */
	if (lp1 -> l_used + n > lp1 -> l_size)
	{
		/* break the current line and let the normal insert do it */
		if ((lp2 = l_break_in_two (lp1, doto, (LPOS)n + NBLOCK)) == NULL)
			return (FALSE);
		lp1 -> l_text[doto] = c;
		lp1 -> l_used++;
		curwp -> w_doto++;
		if (curwp -> w_doto >= lp1 -> l_used)
		{
			curwp -> w_dotp = lp2;
			curwp -> w_doto = 0;
		}
		if (n > 1)
			return (linsert (n - 1, c));    /* handle the rest in normal maner */
	}
	else
	{
		/* Easy: in place   */
		lp2 = lp1;              /* Pretend new line */
		lp2 -> l_used += n;
		cp2 = &lp1 -> l_text[lp1 -> l_used];
		cp1 = cp2 - n;
		while (cp1 != &lp1 -> l_text[doto])
			*--cp2 = *--cp1;
		for (i = 0; i < n; ++i)     /* Add the characters   */
			lp2 -> l_text[doto + i] = c;
		move_ptr (curwp, (A32)n, TRUE, TRUE, TRUE);
	}

	wp = wheadp;                /* Update windows   */
	while (wp != NULL)
	{
		if ((wp -> w_linep == lp1) && (wp -> w_loff >= lp1 -> l_used))
		{
			wp -> w_linep = lp2;
			wp -> w_loff -= lp1 -> l_used;
		}

		/* move dot to next line but not to head line */
		if ((wp -> w_dotp == lp1) && (wp -> w_doto >= lp1 -> l_used) &&
		    (wp -> w_dotp -> l_fp -> l_size != 0))
		{
			wp -> w_dotp = lp2;
			wp -> w_doto -= (lp1 -> l_used - 1);
		}

		if ((wp -> w_markp == lp1) && (wp -> w_marko >= lp1 -> l_used))
		{
			wp -> w_markp = lp2;
			wp -> w_marko -= (lp1 -> l_used - 1);
		}

		wp = wp -> w_wndp;
	}
	l_fix_up (lp1);   /* re-adjust file offsets */
	return (TRUE);
}

/*
* This function deletes n_bytes,
* starting at dot. It understands how to deal
* with end of lines, etc. It returns TRUE if all
* of the characters were deleted, and FALSE if
* they were not (because dot ran into the end of
* the buffer). The "kflag" is TRUE if the text
* should be put in the kill buffer.
*/
bool ldelete (n_bytes, kflag)
A32	n_bytes;
{
	register LINE  *dotp, *lp, *lp_prev, *lp_next;
	register LPOS  doto, l_cnt;
	register WINDOW *wp;
	D8       *cp1, *cp2;
	D32      n_byt, dot_pos;

#if RUNCHK
	/* check that buffer size can be changed */
	if (curbp -> b_flag & BFSLOCK)
	{
		writ_echo (ERR_lock_del);
		return (FALSE);
	}
#endif
	lchange (WFMOVE);
	doto = curwp -> w_doto;
	dotp = curwp -> w_dotp;
	lp_prev = dotp -> l_bp;
	dot_pos = DOT_POS(curwp);

	/* if at the end of the buffer then delete nothing */
	if (dot_pos >= BUF_SIZE(curwp))
	{
		l_fix_up (dotp);    /* re-adjust file offsets */
		return (TRUE);
	}

	/* save dot and mark positions for later restore */
	wp = wheadp;
	while (wp != NULL)
	{
		wp->w_dot_temp = DOT_POS (wp);
		if (wp->w_markp != NULL)  /* mark may not be set */
			wp->w_mark_temp = MARK_POS (wp);
		wp->w_wind_temp = WIND_POS (wp);
		wp = wp -> w_wndp;
	}

	/* is delete wholy within one line? */
	if ((doto + n_bytes) < dotp -> l_used)
	{
		cp1 = &dotp -> l_text[doto];/* Scrunch text.    */
		cp2 = cp1 + n_bytes;

		/* put stuff to delete into the kill buffer */
		if (kflag != FALSE)
		{
			/* Kill?        */
			while (cp1 != cp2)
			{
				if (b_append_c (&sav_buf, *cp1) == FALSE)
					return (FALSE);
				++cp1;
			}
			cp1 = &dotp -> l_text[doto];
		}
		/* kill bytes in the current line */
		while (cp2 < &dotp -> l_text[dotp -> l_used])
			*cp1++ = *cp2++;

		dotp -> l_used -= n_bytes;
	}
	else
	{   /* wholesale delete by moving lines to save buffer */
		if (doto != 0)
		{
			if ((lp = l_break_in_two (dotp, doto, 0l)) == NULL)
				return (FALSE);
		}
		else
			lp = dotp;

		n_byt = n_bytes;
		/* now handle whole lines if necessary */
		while (n_byt > 0)
		{
			lp_next = lp -> l_fp;

			if (n_byt < lp -> l_used)
			{
				/* get last piece of a line */
				lp_next = l_break_in_two (lp, n_byt, 0l);
			}
			n_byt -= lp -> l_used;
			if (kflag)
			{
				/* remove form linked list */
				lp -> l_bp -> l_fp = lp -> l_fp;
				lp -> l_fp -> l_bp = lp -> l_bp;
				/* append it to the save buffer */
				b_append_l (&sav_buf, lp);
			}
			else
				/* if we don't want it, free it */
				lfree (lp);
			lp = lp_next;
		}
	}
	l_fix_up (lp_prev);    /* re-adjust file offsets */

	/* adjust dot and marks in other windows */
	/* this should be ok because the save buffer dosn't disturb l_file_offset */
	wp = wheadp;            /* Fix windows      */
	while (wp != NULL)
	{
		if (curbp == wp -> w_bufp)
		{
			A32    temp;

			/* if dot is before delete position, do nothing */
			if (dot_pos <= (temp = wp -> w_dot_temp))
			{
				/* step back to the previous line */
				wp -> w_doto = 0;
				wp -> w_dotp = lp_prev;

				/* if dot is in deleted range, set to dot position */
				if (temp > dot_pos + n_bytes)
					/* if after deleted range, move back deleted ammount */
					move_ptr (wp, temp - n_bytes, TRUE, TRUE, FALSE);
				else
					/* if in the deleted range, move to curwp dot position */
					move_ptr (wp, dot_pos, TRUE, TRUE, FALSE);
			}
			/* mark may not be set in some windows */
			if (wp -> w_markp != NULL)
			{
				/* do the same for mark */
				if (dot_pos <= (temp = wp->w_mark_temp))
				{
					/* if in or after the deleted range, move to curwp dot position */
					wp -> w_marko = curwp -> w_doto;
					wp -> w_markp = curwp -> w_dotp;

					/* if mark after deleted range */
					if (temp > dot_pos + n_bytes)
					{
						/* if after deleted range, move back deleted ammount */
						/* move dot then swap with mark to produce result */
						move_ptr (wp, temp - n_bytes, TRUE, TRUE, FALSE);
						lp_next = wp -> w_dotp;
						wp -> w_dotp = wp -> w_markp;
						wp -> w_markp = lp_next;
						l_cnt = wp -> w_doto;
						wp -> w_doto = wp -> w_marko;
						wp -> w_marko = l_cnt;
					}
				}
			}
			/* if window position is before delete position, do nothing */
			if (dot_pos <= (temp = wp -> w_wind_temp))
			{
				/* set window position to dot position */
				wp -> w_loff = 0;
				wp -> w_linep = wp -> w_dotp;
				wind_on_dot (wp);
			}
		}
		wp = wp -> w_wndp;
	}
	/* update buffer display */
	if ((blistp -> b_nwnd != 0) &&
	    (blistp -> b_type == BTLIST))
		listbuffers ();
	return (TRUE);
}
/*
*   Replace character at dot position.
*/
void    lreplace (n, c)
int     n;
char    c;
{
	lchange (WFEDIT);
	while (n--)
	{
		DOT_CHAR(curwp) = c & 0xff;
		move_ptr (curwp, 1L, TRUE, FALSE, TRUE);
	}
}

/*
* Replace plen characters before dot with argument string.
*/
bool lrepl_str (plen, rstr, mstr)

register int    plen;           /* length to remove     */
register LINE   *rstr;          /* replace string       */
register LINE   *mstr;          /* mask string       */
{
	register    int    i;       /* used for random characters   */
	register    LINE   *dotp;   /* pointer to line structure */
	register    int    doto;    /* offset into line     */
	register    int     rlen;   /* rplace string length */
	register    char    c;      /* temp storage for char */
	register    char    mask;   /* temp storage for mask */

	/* 
  * make the string lengths match (either pad the line
  * so that it will fit, or scrunch out the excess).
  * be careful with dot's offset.
  */
	doto = curwp -> w_doto;
	rlen = rstr -> l_used;
	if (plen > rlen)
	{
		ldelete ((A32)(plen - rlen), FALSE);
	}
	else if (plen < rlen)
	{
		if (linsert (rlen - plen, ' ') == FALSE)
			return (FALSE);
	}
	curwp -> w_doto = doto;
	dotp = curwp -> w_dotp;     /* save dot line for later */

	/* do the replacement. */
	for (i = 0; i < rlen; i++)
	{
		c = DOT_CHAR(curwp);
		mask = mstr -> l_text[i];
		DOT_CHAR(curwp) = (c & mask) | (rstr -> l_text[i] & ~mask);
		move_ptr (curwp, 1L, TRUE, FALSE, TRUE);
	}
	curwp -> w_doto = doto;
	curwp -> w_dotp = dotp;
	lchange (WFHARD);
	return (TRUE);
}

/*
*   Line fixup.
*   This fixes the 'l_file_offset' variable in
*   each line structure.
*   This is necessary after every change in the size
*   of the buffer.
*/
void l_fix_up (line)

LINE * line;                    /* points to buffer header line */

{
	long    offset;

	offset = line -> l_file_offset;/* starting offset */
	offset += line -> l_used;
	for (;;)
	{
		line = line -> l_fp;
		if (line -> l_size == 0)
			return;
		line -> l_file_offset = offset;
		offset += line -> l_used;
	}
}
