/*	$OpenBSD: paragraph.c,v 1.46 2018/11/17 09:52:34 lum Exp $	*/

/* This file is in the public domain. */

/*
 * Code for dealing with paragraphs and filling. Adapted from MicroEMACS 3.6
 * and GNU-ified by mwm@ucbvax.	 Several bug fixes by blarson@usc-oberon.
 */

#include <sys/queue.h>
#include <ctype.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

#include "def.h"

static int	fillcol = 70;

#define MAXWORD 256

static int	findpara(void);
static int 	do_gotoeop(int, int, int *);

/*
 * Move to start of paragraph.
 * Move backwards by line, checking from the 1st character forwards for the
 * existence a non-space. If a non-space character is found, move to the 
 * preceding line. Keep doing this until a line with only spaces is found or
 * the start of buffer.
 */
/* ARGSUSED */
int
gotobop(int f, int n)
{
	int col, nospace;

	/* the other way... */
	if (n < 0)
		return (gotoeop(f, -n));

	while (n-- > 0) {
		nospace = 0;
		while (lback(curwp->w_dotp) != curbp->b_headp) {
			curwp->w_doto = 0;
			col = 0;

			while (col < llength(curwp->w_dotp) &&
			    (isspace(lgetc(curwp->w_dotp, col))))
				col++;

			if (col >= llength(curwp->w_dotp)) {
				if (nospace)
					break;
			} else
				nospace = 1;

			curwp->w_dotline--;
			curwp->w_dotp = lback(curwp->w_dotp);
		}
	}
	/* force screen update */
	curwp->w_rflag |= WFMOVE;
	return (TRUE);
}

/*
 * Move to end of paragraph.
 * See comments for gotobop(). Same, but moving forwards.
 */
/* ARGSUSED */
int
gotoeop(int f, int n)
{
	int i;

	return(do_gotoeop(f, n, &i));
}

int
do_gotoeop(int f, int n, int *i)
{
	int col, nospace, j = 0;

	/* the other way... */
	if (n < 0)
		return (gotobop(f, -n));

	/* for each one asked for */
	while (n-- > 0) {
		*i = ++j;
		nospace = 0;
		while (lforw(curwp->w_dotp) != curbp->b_headp) {
			col = 0;
			curwp->w_doto = 0;

			while (col < llength(curwp->w_dotp) &&
			    (isspace(lgetc(curwp->w_dotp, col))))
				col++;

			if (col >= llength(curwp->w_dotp)) {
				if (nospace)
					break;
			} else
				nospace = 1;

			curwp->w_dotp = lforw(curwp->w_dotp);
			curwp->w_dotline++;

		}
	}
	/* do not continue after end of buffer */
	if (lforw(curwp->w_dotp) == curbp->b_headp) {
		gotoeol(FFRAND, 1);
		curwp->w_rflag |= WFMOVE;
		return (FALSE);
	}

	/* force screen update */
	curwp->w_rflag |= WFMOVE;
	return (TRUE);
}

/*
 * Justify a paragraph.  Fill the current paragraph according to the current
 * fill column.
 */
/* ARGSUSED */
int
fillpara(int f, int n)
{
	int	 c;		/* current char during scan		*/
	int	 wordlen;	/* length of current word		*/
	int	 clength;	/* position on line during fill		*/
	int	 i;		/* index during word copy		*/
	int	 eopflag;	/* Are we at the End-Of-Paragraph?	*/
	int	 firstflag;	/* first word? (needs no space)		*/
	int	 newlength;	/* tentative new line length		*/
	int	 eolflag;	/* was at end of line			*/
	int	 retval;	/* return value				*/
	struct line	*eopline;	/* pointer to line just past EOP	*/
	char	 wbuf[MAXWORD];	/* buffer for current word		*/

	if (n == 0)
		return (TRUE);

	undo_boundary_enable(FFRAND, 0);

	/* record the pointer to the line just past the EOP */
	(void)gotoeop(FFRAND, 1);
	if (curwp->w_doto != 0) {
		/* paragraph ends at end of buffer */
		(void)lnewline();
		eopline = lforw(curwp->w_dotp);
	} else
		eopline = curwp->w_dotp;

	/* and back top the beginning of the paragraph */
	(void)gotobop(FFRAND, 1);

	/* initialize various info */
	while (inword() == 0 && forwchar(FFRAND, 1));

	clength = curwp->w_doto;
	wordlen = 0;

	/* scan through lines, filling words */
	firstflag = TRUE;
	eopflag = FALSE;
	while (!eopflag) {

		/* get the next character in the paragraph */
		if ((eolflag = (curwp->w_doto == llength(curwp->w_dotp)))) {
			c = ' ';
			if (lforw(curwp->w_dotp) == eopline)
				eopflag = TRUE;
		} else
			c = lgetc(curwp->w_dotp, curwp->w_doto);

		/* and then delete it */
		if (ldelete((RSIZE) 1, KNONE) == FALSE && !eopflag) {
			retval = FALSE;
			goto cleanup;
		}

		/* if not a separator, just add it in */
		if (c != ' ' && c != '\t') {
			if (wordlen < MAXWORD - 1)
				wbuf[wordlen++] = c;
			else {
				/*
				 * You lose chars beyond MAXWORD if the word
				 * is too long. I'm too lazy to fix it now; it
				 * just silently truncated the word before,
				 * so I get to feel smug.
				 */
				ewprintf("Word too long!");
			}
		} else if (wordlen) {

			/* calculate tentative new length with word added */
			newlength = clength + 1 + wordlen;

			/*
			 * if at end of line or at doublespace and previous
			 * character was one of '.','?','!' doublespace here.
			 * behave the same way if a ')' is preceded by a
			 * [.?!] and followed by a doublespace.
			 */
			if (dblspace && (!eopflag && ((eolflag ||
			    curwp->w_doto == llength(curwp->w_dotp) ||
			    (c = lgetc(curwp->w_dotp, curwp->w_doto)) == ' '
			    || c == '\t') && (ISEOSP(wbuf[wordlen - 1]) ||
			    (wbuf[wordlen - 1] == ')' && wordlen >= 2 &&
			    ISEOSP(wbuf[wordlen - 2])))) &&
			    wordlen < MAXWORD - 1))
				wbuf[wordlen++] = ' ';

			/* at a word break with a word waiting */
			if (newlength <= fillcol) {
				/* add word to current line */
				if (!firstflag) {
					(void)linsert(1, ' ');
					++clength;
				}
				firstflag = FALSE;
			} else {
				if (curwp->w_doto > 0 &&
				    lgetc(curwp->w_dotp, curwp->w_doto - 1) == ' ') {
					curwp->w_doto -= 1;
					(void)ldelete((RSIZE) 1, KNONE);
				}
				/* start a new line */
				(void)lnewline();
				clength = 0;
			}

			/* and add the word in in either case */
			for (i = 0; i < wordlen; i++) {
				(void)linsert(1, wbuf[i]);
				++clength;
			}
			wordlen = 0;
		}
	}
	/* and add a last newline for the end of our new paragraph */
	(void)lnewline();

	/*
	 * We really should wind up where we started, (which is hard to keep
	 * track of) but I think the end of the last line is better than the
	 * beginning of the blank line.
	 */
	(void)backchar(FFRAND, 1);
	retval = TRUE;
cleanup:
	undo_boundary_enable(FFRAND, 1);
	return (retval);
}

/*
 * Delete n paragraphs. Move to the beginning of the current paragraph, or if
 * the cursor is on an empty line, move down the buffer to the first line with
 * non-space characters. Then mark n paragraphs and delete.
 */
/* ARGSUSED */
int
killpara(int f, int n)
{
	int	lineno, status;

	if (n == 0)
		return (TRUE);

	if (findpara() == FALSE)
		return (TRUE);

	/* go to the beginning of the paragraph */
	(void)gotobop(FFRAND, 1);

	/* take a note of the line number for after deletions and set mark */
	lineno = curwp->w_dotline;
	curwp->w_markp = curwp->w_dotp;
	curwp->w_marko = curwp->w_doto;

	(void)gotoeop(FFRAND, n);

	if ((status = killregion(FFRAND, 1)) != TRUE)
		return (status);

	curwp->w_dotline = lineno;
	return (TRUE);
}

/*
 * Mark n paragraphs starting with the n'th and working our way backwards.
 * This leaves the cursor at the beginning of the paragraph where markpara()
 * was invoked.
 */
/* ARGSUSED */
int
markpara(int f, int n)
{
	int i = 0;

	if (n == 0)
		return (TRUE);

	clearmark(FFARG, 0);

	if (findpara() == FALSE)
		return (TRUE);

	(void)do_gotoeop(FFRAND, n, &i);

	/* set the mark here */
	curwp->w_markp = curwp->w_dotp;
	curwp->w_marko = curwp->w_doto;

	(void)gotobop(FFRAND, i);

	return (TRUE);
}

/*
 * Transpose the current paragraph with the following paragraph. If invoked
 * multiple times, transpose to the n'th paragraph. If invoked between 
 * paragraphs, move to the previous paragraph, then continue.
 */
/* ARGSUSED */
int
transposepara(int f, int n)
{
	int	i = 0, status;
	char	flg;

	if (n == 0)
		return (TRUE);

	undo_boundary_enable(FFRAND, 0);

	/* find a paragraph, set mark, then goto the end */
	gotobop(FFRAND, 1);
	curwp->w_markp = curwp->w_dotp;
	curwp->w_marko = curwp->w_doto;
	(void)gotoeop(FFRAND, 1);

	/* take a note of buffer flags - we may need them */
	flg = curbp->b_flag;	

	/* clean out kill buffer then kill region */
	kdelete();
	if ((status = killregion(FFRAND, 1)) != TRUE)
		return (status);

	/* 
	 * Now step through n paragraphs. If we reach the end of buffer,
	 * stop and paste the killed region back, then display a message.
	 */
	if (do_gotoeop(FFRAND, n, &i) == FALSE) {
		ewprintf("Cannot transpose paragraph, end of buffer reached.");
		(void)gotobop(FFRAND, i);
		(void)yank(FFRAND, 1);
		curbp->b_flag = flg;	
		return (FALSE);
	}
	(void)yank(FFRAND, 1);

	undo_boundary_enable(FFRAND, 1);

	return (TRUE);
}

/*
 * Go down the buffer until we find a line with non-space characters.
 */
int
findpara(void)
{
	int	col, nospace = 0;

	/* we move forward to find a para to mark */
	do {
		curwp->w_doto = 0;
		col = 0;

		/* check if we are on a blank line */
		while (col < llength(curwp->w_dotp)) {
			if (!isspace(lgetc(curwp->w_dotp, col)))
				nospace = 1;
			col++;
		}
		if (nospace)
			break;

		if (lforw(curwp->w_dotp) == curbp->b_headp)
			return (FALSE);

		curwp->w_dotp = lforw(curwp->w_dotp);	
		curwp->w_dotline++;
	} while (1);

	return (TRUE);
}

/*
 * Insert char with work wrap.  Check to see if we're past fillcol, and if so,
 * justify this line.  As a last step, justify the line.
 */
/* ARGSUSED */
int
fillword(int f, int n)
{
	char	c;
	int	col, i, nce;

	for (i = col = 0; col <= fillcol; ++i, ++col) {
		if (i == curwp->w_doto)
			return selfinsert(f, n);
		c = lgetc(curwp->w_dotp, i);
		if (c == '\t'
#ifdef NOTAB
		    && !(curbp->b_flag & BFNOTAB)
#endif
			)
			col |= 0x07;
		else if (ISCTRL(c) != FALSE)
			++col;
	}
	if (curwp->w_doto != llength(curwp->w_dotp)) {
		(void)selfinsert(f, n);
		nce = llength(curwp->w_dotp) - curwp->w_doto;
	} else
		nce = 0;
	curwp->w_doto = i;

	if ((c = lgetc(curwp->w_dotp, curwp->w_doto)) != ' ' && c != '\t')
		do {
			(void)backchar(FFRAND, 1);
		} while ((c = lgetc(curwp->w_dotp, curwp->w_doto)) != ' ' &&
		    c != '\t' && curwp->w_doto > 0);

	if (curwp->w_doto == 0)
		do {
			(void)forwchar(FFRAND, 1);
		} while ((c = lgetc(curwp->w_dotp, curwp->w_doto)) != ' ' &&
		    c != '\t' && curwp->w_doto < llength(curwp->w_dotp));

	(void)delwhite(FFRAND, 1);
	(void)lnewline();
	i = llength(curwp->w_dotp) - nce;
	curwp->w_doto = i > 0 ? i : 0;
	curwp->w_rflag |= WFMOVE;
	if (nce == 0 && curwp->w_doto != 0)
		return (fillword(f, n));
	return (TRUE);
}

/*
 * Set fill column to n for justify.
 */
int
setfillcol(int f, int n)
{
	char buf[32], *rep;
	const char *es;
	int nfill;

	if ((f & FFARG) != 0) {
		fillcol = n;
	} else {
		if ((rep = eread("Set fill-column: ", buf, sizeof(buf),
		    EFNEW | EFCR)) == NULL)
			return (ABORT);
		else if (rep[0] == '\0')
			return (FALSE);
		nfill = strtonum(rep, 0, INT_MAX, &es);
		if (es != NULL) {
			dobeep();
			ewprintf("Invalid fill column: %s", rep);
			return (FALSE);
		}
		fillcol = nfill;
		ewprintf("Fill column set to %d", fillcol);
	}
	return (TRUE);
}

int
sentencespace(int f, int n)
{
	if (f & FFARG)
		dblspace = n > 1;
	else
		dblspace = !dblspace;

	return (TRUE);
}
